TypeFactory.java

/*
 * Copyright © 2025 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 iu.type;

import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.function.Consumer;

import edu.iu.type.IuType;
import edu.iu.type.IuTypeKey;

/**
 * Provides fully formed instances of {@link TypeTemplate} to
 * {@link TypeSpi#resolveType(Type)}.
 */
final class TypeFactory {

	private static final Map<Class<?>, TypeTemplate<?, ?>> RAW_TYPES = new WeakHashMap<>();
	private static final ThreadLocal<Map<IuTypeKey, TypeTemplate<?, ?>>> PENDING_GENERIC_TYPES = new ThreadLocal<>();
	private static final ThreadLocal<Map<Class<?>, TypeTemplate<?, ?>>> PENDING_RAW_TYPES = new ThreadLocal<>();

	/**
	 * Clears all indexed and cached data.
	 * 
	 * <p>
	 * <em>Should</em> be used:
	 * </p>
	 * 
	 * <ul>
	 * <li>Between internal unit tests to reset state.</li>
	 * <li>As an administrative function from a container management interface.</li>
	 * </ul>
	 */
	static void clear() {
		synchronized (RAW_TYPES) {
			RAW_TYPES.clear();
		}
	}

	/**
	 * Gets the type erasure for a generic type.
	 * 
	 * @param type raw or generic type
	 * @return type erasure
	 * @see IuType#erasedClass()
	 */
	static Class<?> getErasedClass(Type type) {
		if (type instanceof Class)
			return (Class<?>) type;

		record Node(Type type, int array) {
		}

		Deque<Node> todo = new ArrayDeque<>();
		var node = new Node(type, 0);
		todo.push(node);

		while (!todo.isEmpty()) {
			node = todo.pop();

			// JLS 21 4.6: The erasure of an array type T[] is |T|[].
			if (node.type instanceof GenericArrayType)
				todo.push(new Node(((GenericArrayType) node.type).getGenericComponentType(), node.array + 1));

			// JLS 21 4.6: The erasure of a parameterized type (§4.5) G<T1,...,Tn> is |G|.
			else if (node.type instanceof ParameterizedType)
				todo.push(new Node(((ParameterizedType) node.type).getRawType(), node.array));

			// JLS 21 4.6: The erasure of a type variable (§4.4) is the erasure of its
			// leftmost bound
			else if (node.type instanceof TypeVariable)
				/*
				 * This method implements the logic for a **reference** type variable. That is,
				 * The type variable at the end of a reference chain. For example, The type
				 * variable E in the parameterized type ArrayList<E> refers to the variable E in
				 * List<E>, which in turn refers to the variable T in Iterable<T>. If the
				 * introspected instance is an ArrayList, but the type of the element being
				 * introspected is Iterable<?> and the purpose of introspection is to determine
				 * the item type for elements in the iteration, this method is only responsible
				 * for resolving the type variable E on ArrayList<String> to the class String,
				 * not for derefencing the two type variable references it takes to reach that
				 * reference variable.
				 */
				todo.push(new Node(((TypeVariable<?>) node.type).getBounds()[0], node.array));

			/*
			 * During capture conversion, a wildcard type arguments translates to a type
			 * variable with bounds equivalent to the upper bound of upper bound of the
			 * wildcard. So, may be erased to its left-most upper bound for determining the
			 * equivalent raw type of a type argument.
			 */
			// JLS 21 5.1.10:
			// If Ti is a wildcard type argument of the form ? extends Bi, then Si is a
			// fresh type variable whose upper bound is glb(Bi, Ui[A1:=S1,...,An:=Sn]) and
			// whose lower bound is the null type.
			// glb(V1,...,Vm) is defined as V1 & ... & Vm
			else if (node.type instanceof WildcardType)
				todo.push(new Node(((WildcardType) node.type).getUpperBounds()[0], node.array));

			else if (!(node.type instanceof Class))
				// java.lang.reflect.Type should be considered effectively sealed to the only
				// the types handled above. Custom implementations are not covered by JLS and
				// cannot be generated by the compiler.
				throw new IllegalArgumentException(
						"Invalid generic type, must be ParameterizedType, GenericArrayType, TypeVariable, or WildcardType");
		}

		var erasure = (Class<?>) node.type;
		for (var i = 0; i < node.array; i++)
			erasure = Array.newInstance(erasure, 0).getClass();
		return erasure;
	}

	/**
	 * Applies a front-loaded pre-initialization cache to the current thread to
	 * prevent head recursion while resolving type templates for the global raw type
	 * cache.
	 * 
	 * <p>
	 * This encapsulates both raw global cache and tail-recursive hierarchy
	 * resolution with global cache resolution.
	 * </p>
	 * 
	 * @param <T>      raw type
	 * @param rawClass raw class
	 * @param register pre-initialization hook consumer. <em>Must</em> implement all
	 *                 cache-miss resolution logic; will be passed a value to supply
	 *                 to {@link TypeTemplate#TypeTemplate(Class, Consumer)} for
	 *                 registering the new instance on the current thread before
	 *                 applying internal constructor logic. At least one
	 *                 {@link TypeTemplate} instance <em>must</em> be created, the
	 *                 last instance created will be applied to the global cache and
	 *                 returned. Exceptions thrown from
	 *                 {@link Consumer#accept(Object)} will pending
	 *                 {@link TypeTemplate}s cached on the current thread to be
	 *                 discarded.
	 * @return cached or resolved {@link TypeTemplate} instance
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	static <T> TypeTemplate<?, T> applyRawTypeCache(Class<T> rawClass,
			Consumer<Consumer<TypeTemplate<?, ?>>> register) {
		TypeTemplate resolvedType = RAW_TYPES.get(rawClass);
		if (resolvedType != null)
			return resolvedType;

		final var threadRawTypes = PENDING_RAW_TYPES.get();
		final Map<Class<?>, TypeTemplate<?, ?>> pendingRawTypes;
		if (threadRawTypes == null)
			PENDING_RAW_TYPES.set(pendingRawTypes = new HashMap<>());
		else
			pendingRawTypes = threadRawTypes;

		resolvedType = pendingRawTypes.get(rawClass);
		if (resolvedType != null)
			return resolvedType;

		try {
			register.accept(s -> pendingRawTypes.put(rawClass, s));
			resolvedType = Objects.requireNonNull(pendingRawTypes.get(rawClass));

			synchronized (RAW_TYPES) {
				RAW_TYPES.put(rawClass, resolvedType);
			}

			return resolvedType;
		} finally {
			if (threadRawTypes == null)
				PENDING_RAW_TYPES.remove();
		}
	}

	/**
	 * Gets a resolved type facade for a raw class.
	 * 
	 * @param <T>      raw type
	 * @param rawClass raw class
	 * @return {@link TypeFacade}
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	static <T> TypeTemplate<?, T> resolveRawClass(Class<T> rawClass) {
		// Box pattern helps maintain hierarchy reference chain
		// by facilitating in-place swap for finer-grained references
		// as discovered in the type hierarchy
		class Box {
			IuType<?, ? super T> value;

			Box(IuType<?, ? super T> value) {
				this.value = value;
			}
		}

		return applyRawTypeCache(rawClass, preInitHook -> {
			final var resolvedType = new TypeTemplate<>(rawClass, preInitHook);

			// LinkedHashMap maintains object-first order
			final Map<Class<?>, Box> hierarchyByErasure = new LinkedHashMap<>();

			// Create a top-down (Object-first) traversal queue for all classes
			// extended and interfaces implemented by the raw class
			final Deque<Type> hierarchy = new ArrayDeque<>();
			{
				final Deque<Type> todo = new ArrayDeque<>();
				todo.push(rawClass); // bottom-up traversal stack

				while (!todo.isEmpty()) {
					final var type = todo.pop();
					if (type != rawClass) // don't include raw class in hierarchy
						hierarchy.push(type); // push in reverse of traversal order

					final var erasedClass = getErasedClass(type);

					// truncate hierarchy at Object for Java language-native types
					if (erasedClass == Object.class)
						continue;
					else if (erasedClass == Class.class)
						todo.push(Object.class);

					else {
						// traversal stack will be reversed, so push Object before interfaces
						// : last in stack traversal results in first queued
						final var superType = erasedClass.getGenericSuperclass();
						if (superType != null)
							todo.push(superType);

						for (final var implemented : erasedClass.getGenericInterfaces())
							todo.push(implemented);
					}
				}
			}

			// This loop trusts getHierarchy() to return Object-first so super classes may
			// be resolved first, in place, while scanning deeper in the hierarchy
			for (var superType : hierarchy) {
				final var erasedSuperClass = (Class<? super T>) getErasedClass(superType);
				final var rawSuperType = applyRawTypeCache(erasedSuperClass, superPreInitHook -> {
					var pendingRawSuperType = new TypeTemplate<>(erasedSuperClass, superPreInitHook);

					// Build and cache new template on the fly. This is possible since we
					// already have the interim type's full hierarchy available.
					final Deque<IuType<?, ? super T>> superHierarchy = new ArrayDeque<>();
					for (var superTypeBox : hierarchyByErasure.values()) {
						final var inheritedSuperType = superTypeBox.value;
						final var erasedClass = inheritedSuperType.erasedClass();

						// filter super-type hierarchy to only include assignable elements
						// Note that interfaces do not declare Object as superclass
						// https://docs.oracle.com/javase/specs/jls/se21/html/jls-9.html
						if ((erasedClass != Object.class || !erasedSuperClass.isInterface()) //
								&& erasedClass.isAssignableFrom(erasedSuperClass))
							superHierarchy.push(inheritedSuperType);
					}

					pendingRawSuperType.sealHierarchy((Iterable) superHierarchy);
				});

				TypeTemplate<?, ? super T> superTypeTemplate;
				if (superType == erasedSuperClass)
					superTypeTemplate = (TypeTemplate<?, ? super T>) rawSuperType;
				else
					superTypeTemplate = (TypeTemplate<?, ? super T>) resolveType(superType);

				// replace previously resolved super type references with references through the
				// declaring extended class or implemented interface
				for (var inheritedSuperType : superTypeTemplate.hierarchy()) {
					var inheritedErasedClass = inheritedSuperType.erasedClass();
					var inheritedBox = Objects.requireNonNull(hierarchyByErasure.get(inheritedErasedClass));
					inheritedBox.value = inheritedSuperType;
				}

				hierarchyByErasure.put(erasedSuperClass, new Box(superTypeTemplate));
			}

			final Deque<IuType<?, ? super T>> hierarchyTemplates = new ArrayDeque<>();
			for (final var hierarchyReference : hierarchyByErasure.values())
				hierarchyTemplates.push(hierarchyReference.value);

			resolvedType.sealHierarchy(hierarchyTemplates);
		});
	}

	/**
	 * Resolves a facade for a generic type.
	 * 
	 * @param type generic type
	 * @return {@link TypeFacade}
	 */
	static TypeTemplate<?, ?> resolveType(Type type) {
		Objects.requireNonNull(type, "type");
		if (type instanceof Class)
			return resolveRawClass((Class<?>) type);
		else {
			// Establish base case for self and loop references
			final var restorePendingGenericTypes = PENDING_GENERIC_TYPES.get();
			try {
				final Map<IuTypeKey, TypeTemplate<?, ?>> pendingGenericTypes;
				if (restorePendingGenericTypes == null) {
					pendingGenericTypes = new HashMap<>();
					PENDING_GENERIC_TYPES.set(pendingGenericTypes);
				} else
					pendingGenericTypes = restorePendingGenericTypes;

				final var key = IuTypeKey.of(type);
				final var resolvedType = pendingGenericTypes.get(key);
				if (resolvedType != null)
					return resolvedType;

				return new TypeTemplate<>(s -> pendingGenericTypes.put(key, s), type,
						resolveRawClass(getErasedClass(type)));
			} finally {
				if (restorePendingGenericTypes == null)
					PENDING_GENERIC_TYPES.remove();
			}
		}
	}

	private TypeFactory() {
	}

}