IuVisitor.java

/*
 * Copyright © 2024 Indiana University
 * All rights reserved.
 *
 * BSD 3-Clause License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * - Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * 
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * 
 * - Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package edu.iu;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Optional;
import java.util.Queue;
import java.util.Spliterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Implements a basic visitor pattern for tracking disparate uniform instances
 * of a specific element type.
 * 
 * <p>
 * This resource is thread-safe and intended for use in high-volume and
 * time-sensitive component initialization scenarios. Elements are weakly held,
 * so will may only be visited until cleared by the garbage collector.
 * </p>
 * 
 * <p>
 * <img src="doc-files/Visitor.svg" alt="UML Class Diagram" />
 * </p>
 * 
 * @param <T> element type
 */
public class IuVisitor<T> implements Consumer<T> {

	private class ElementSplitter implements Spliterator<T> {
		private final Spliterator<Reference<T>> elementSpliterator;

		private ElementSplitter(Spliterator<Reference<T>> elementSpliterator) {
			this.elementSpliterator = elementSpliterator;
		}

		@Override
		public boolean tryAdvance(Consumer<? super T> action) {
			class Box {
				boolean accepted;
			}
			final var box = new Box();

			while (!box.accepted)
				if (!elementSpliterator.tryAdvance(ref -> {
					final var element = ref.get();
					if (element != null) {
						action.accept(element);
						box.accepted = true;
					}
				}))
					return false;

			return box.accepted;
		}

		@Override
		public Spliterator<T> trySplit() {
			final var split = elementSpliterator.trySplit();
			if (split != null)
				return new ElementSplitter(split);
			else
				return null;
		}

		@Override
		public long estimateSize() {
			if (elementSpliterator.hasCharacteristics(SIZED))
				return elementSpliterator.estimateSize();
			else
				return elements.size();
		}

		@Override
		public int characteristics() {
			return elementSpliterator.characteristics() | SIZED;
		}
	}

	private final Queue<Reference<T>> elements = new ConcurrentLinkedQueue<>();

	/**
	 * Default constructor.
	 */
	public IuVisitor() {
	}

	/**
	 * Applies a function to each element until a generic condition is satisfied.
	 * 
	 * @param <V>     value type
	 * @param visitor Function to {@link Function#apply(Object) apply} each element
	 *                to until an {@link Optional} value is returned. {@code null}
	 *                will be {@link Function#apply(Object) applied} last if no
	 *                elements result in an {@link Optional} value. The function
	 *                <em>may</em> return {@code null} to continue to the next
	 *                element, and <em>may</em> always return {@code null} to always
	 *                visit all elements. A non-null {@link Optional} result
	 *                indicates the visited element satisfied a generic condition of
	 *                some sort and so no other elements should be visited; the
	 *                {@link Optional} is immediately returned as-is.
	 * @return {@link Optional} result of the first terminal condition satisfied;
	 *         null if a terminal condition was not met.
	 */
	public <V> Optional<V> visit(Function<T, Optional<V>> visitor) {
		final var elementIterator = elements.iterator();
		while (elementIterator.hasNext()) {
			final var elementReference = elementIterator.next();
			final var element = elementReference.get();
			if (element == null) {
				elementIterator.remove();
				continue;
			}

			final var optionalValue = visitor.apply(element);
			if (optionalValue != null)
				return optionalValue;
		}

		return visitor.apply(null);
	}

	/**
	 * Accepts an element to be observed.
	 * 
	 * @param element to observe
	 */
	@Override
	public void accept(T element) {
		elements.add(new WeakReference<>(element));
	}

	/**
	 * Removes an element from observation queue without waiting for the garbage
	 * collector to clear it.
	 * 
	 * <p>
	 * This method does not tear down or take any other action on the element, the
	 * controlling component <em>should</em> handle all tear down logic related to
	 * this visitor instance prior to clearing the reference, typically invoking
	 * this method last in a managed instance's lifecycle teardown process.
	 * </p>
	 * 
	 * <p>
	 * This method has no effect if the element is not in the observation queue.
	 * </p>
	 * 
	 * <p>
	 * <strong>API Note:</strong> Since elements are {@link WeakReference weakly
	 * held}, this method is only necessary when hooking in to an external instance
	 * management mechanism. Typically, instances may simply be discarded rather
	 * then explicitly cleared.
	 * </p>
	 * 
	 * @param element element to clear
	 */
	public void clear(T element) {
		final var elementIterator = elements.iterator();
		while (elementIterator.hasNext()) {
			final var elementReference = elementIterator.next();
			final var deref = elementReference.get();
			if (deref == null || element == deref) {
				elementIterator.remove();
				continue;
			}
		}
	}

	/**
	 * Gets a {@link IuAsynchronousSubject} originated by non-cleared references to
	 * accepted elements.
	 * 
	 * <p>
	 * Each call to this method returns an independent subject instance. The
	 * controller responsible for providing elements to the visitor <em>must</em>
	 * independently provide the same values to, and close its own, subject instance
	 * to ensure continuity for subscribers.
	 * </p>
	 * 
	 * @return {@link IuAsynchronousSubject}
	 */
	public IuAsynchronousSubject<T> subject() {
		return new IuAsynchronousSubject<>(() -> new ElementSplitter(elements.spliterator()));
	}

}