TypeTemplate.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.beans.Introspector;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayDeque;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.function.Consumer;
import edu.iu.IuException;
import edu.iu.IuObject;
import edu.iu.IuVisitor;
import edu.iu.type.InstanceReference;
import edu.iu.type.IuConstructor;
import edu.iu.type.IuReferenceKind;
import edu.iu.type.IuType;
import edu.iu.type.IuTypeReference;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
/**
* Represents the internal structure of a {@link TypeFacade}.
*
* <p>
* Each template is a standalone representation of a single generic type,
* potentially paired with a raw template representing its erasure.
* </p>
*
* <p>
* Note that {@link TypeTemplate} is not sterotyped as a hash key, but
* {@link IuType} is. This hash key behavior comes from same-instance identity
* default {@link #hashCode()} and {@link #equals(Object)} implementations of
* instances managed by {@link TypeFactory}. Since ClassLoading data is loaded
* exactly once and remains static once loaded, type introspection instances
* match that load-once static behavior. It is expected that each raw type
* (primitive, class, interface, enum, record, etc) has exactly one
* {@link TypeTemplate} instance, and that each {@link TypeTemplate} instance
* backed by a generic type contains a {@link TypeFacade} backed by a singleton
* raw type instances representing its {@link #erase() type erasure}. This
* behavior mirrors the hash key behavior of {@link Type}, so internal checks
* for equality (and inequality) may use == (and !=).
* </p>
*
* <p>
* Note also that Java does not constrain the number of {@link Type} instances,
* and considers those instances other than {@link Class} to be disposable.
* {@link TypeFactory} does not manage {@link TypeTemplate} instances for
* generic type markers once returned to the application.
* </p>
*
* <h2>Initialization Order</h2>
* <p>
* Type templates are initialized in two phases, managed by {@link TypeFactory}.
* </p>
* <ol>
* <li>Declared elements</li>
* <li>Inherited elements</li>
* </ol>
*
* <h3>Declared Elements</h3>
* <p>
* Resolved in order upon instantiation:
* </p>
* <ol>
* <li>{@link ElementBase#ElementBase(Consumer)} {@code preInitHook} binds raw
* {@link Class} instances to {@link TypeFactory}{@code #RAW_TYPES}</li>
* <li>{@link AnnotatedElementBase#AnnotatedElementBase(AnnotatedElement, Consumer)}
* binds {@link #annotatedElement}</li>
* <li>{@link DeclaredElementBase#DeclaredElementBase(AnnotatedElement, Consumer, Type, TypeTemplate)}
* binds:
* <ul>
* <li>{@link #type}</li>
* <li>{@link #declaringType()}, potentially null or unsealed</li>
* </ul>
* </li>
* <li>Apply actual type arguments from {@link ParameterizedType}</li>
* <li>{@link #erase()}</li>
* <li>{@link #constructors()}</li>
* </ol>
*
* <h3>Inherited Elements</h3>
* <p>
* Resolved by {@link #sealHierarchy(Iterable)}, order incidental
* </p>
* <ul>
* <li>{@link #fields()}</li>
* <li>{@link #properties()}</li>
* <li>{@link #methods()}</li>
* <li>{@link #typeParameters()}</li>
* </ul>
*
* @param <D> declaring type
* @param <T> raw or generic type
*/
final class TypeTemplate<D, T> extends DeclaredElementBase<D, Class<T>> implements IuType<D, T>, ParameterizedFacade {
@SuppressWarnings("unchecked")
private static <D> TypeTemplate<?, D> resolveDeclaringType(Class<?> enclosed) {
final var declaringClass = enclosed.getDeclaringClass();
if (declaringClass == null)
return null;
return (TypeTemplate<?, D>) TypeFactory.resolveRawClass(declaringClass);
}
// Declared
private IuType<D, T> erasedType;
private Iterable<TypeFacade<T, ?>> enclosedTypes;
private Iterable<ConstructorFacade<T>> constructors;
// Inherited
private Iterable<TypeFacade<?, ? super T>> hierarchy;
private Iterable<FieldFacade<? super T, ?>> fields;
private Iterable<PropertyFacade<? super T, ?>> properties;
private Iterable<MethodFacade<? super T, ?>> methods;
// Parameterized
private final ParameterizedElement parameterizedElement = new ParameterizedElement();
// Instance management
private final IuVisitor<InstanceReference<T>> instanceReferences = new IuVisitor<>();
private TypeTemplate(Class<T> annotatedElement, Consumer<TypeTemplate<?, ?>> preInitHook, Type type,
TypeTemplate<D, T> erasedType) {
super(annotatedElement, preInitHook, type, resolveDeclaringType(annotatedElement));
if (declaringType == null || isStatic())
initializeDeclared(erasedType);
else
declaringType.template.postInit(() -> initializeDeclared(erasedType));
}
/**
* Raw class constructor intended for use only by {@link TypeFactory}.
*
* @param rawClass raw class
* @param preInitHook receives a handle to {@code this} after binding the
* annotated element but before initializing and members
*/
TypeTemplate(Class<T> rawClass, Consumer<TypeTemplate<?, ?>> preInitHook) {
this(rawClass, preInitHook, rawClass, null);
}
/**
* Generic type constructor intended for use only by {@link TypeFactory}.
*
* @param preInitHook receives a handle to {@code this} after binding the
* annotated element but before initializing and members
* @param type generic type; <em>must not</em> be a class
* @param erasedType pre-calculated raw type template; a {@link TypeTemplate}
* cannot be created for a generic type without a
* fully-formed instance of its type erasure, provided as an
* argument to this parameter
*/
TypeTemplate(Consumer<TypeTemplate<?, ?>> preInitHook, Type type, TypeTemplate<D, T> erasedType) {
this(erasedType.erasedClass(), preInitHook, type, erasedType);
assert !(type instanceof Class) : type;
assert erasedType.erasedClass() == TypeFactory.getErasedClass(type)
: erasedType + " " + TypeUtils.printType(type);
erasedType.postInit(() -> sealHierarchy(erasedType.hierarchy));
}
private boolean isNative() {
final var packageName = annotatedElement.getPackageName();
final var targetModule = annotatedElement.getModule();
final var typeImplModule = getClass().getModule();
return IuObject.isPlatformName(name()) //
|| targetModule == typeImplModule //
|| !targetModule.isOpen(packageName, typeImplModule);
}
private void initializeDeclared(TypeTemplate<D, T> erasedType) {
if (declaringType != null && !isStatic())
parameterizedElement.apply(declaringType.template.typeParameters());
if (erasedType == null) {
this.erasedType = this;
initializeConstructors();
} else {
this.erasedType = new TypeFacade<D, T>(erasedType, this, IuReferenceKind.ERASURE);
erasedType.postInit(() -> {
initializeConstructors();
});
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void initializeEnclosedTypes() {
Queue<TypeFacade<T, ?>> enclosedTypes = new ArrayDeque<>();
if (!isNative()) {
Class<?>[] enclosedClasses = annotatedElement.getDeclaredClasses();
postInit(() -> {
for (var enclosedClass : enclosedClasses) {
final var enclosedType = TypeFactory.resolveRawClass(enclosedClass);
enclosedTypes.offer(new TypeFacade(enclosedType, this, IuReferenceKind.ENCLOSING_TYPE));
}
});
}
this.enclosedTypes = enclosedTypes;
}
@SuppressWarnings("unchecked")
private void initializeConstructors() {
Queue<ConstructorFacade<T>> constructors = new ArrayDeque<>();
if (!isNative() //
&& !annotatedElement.isInterface() //
&& !annotatedElement.isEnum())
for (var constructor : annotatedElement.getDeclaredConstructors())
// _unchecked warning_: see source for #getDeclaredConstructors()
// => This cast is safe as of Java 17
constructors.offer(new ConstructorFacade<>((Constructor<T>) constructor, this));
this.constructors = constructors;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Iterable<FieldFacade<? super T, ?>> initializeFields() {
Queue<FieldFacade<? super T, ?>> rv = new ArrayDeque<>();
if (!isNative()) //
for (var field : annotatedElement.getDeclaredFields()) {
TypeTemplate<?, ?> fieldType;
if (field.getType() == annotatedElement)
fieldType = this;
else
fieldType = TypeFactory.resolveType(field.getGenericType());
rv.offer(new FieldFacade(field, fieldType, this));
}
for (var superType : hierarchy)
superType.template.postInit(() -> {
for (var inheritedField : superType.template.fields)
rv.offer(inheritedField);
});
return rv;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Iterable<PropertyFacade<? super T, ?>> initializeProperties() {
Queue<PropertyFacade<? super T, ?>> rv = new ArrayDeque<>();
if (!isNative()) //
for (var property : IuException.unchecked(() -> Introspector.getBeanInfo(annotatedElement))
.getPropertyDescriptors()) {
if (property.getName().equals("class"))
continue;
var readMethod = property.getReadMethod();
var writeMethod = property.getWriteMethod();
Type propertyType;
if (readMethod != null)
propertyType = readMethod.getGenericReturnType();
else if (writeMethod != null)
propertyType = writeMethod.getGenericParameterTypes()[0];
else
continue;
TypeTemplate<?, T> propertyTypeTemplate;
if (propertyType == annotatedElement)
propertyTypeTemplate = this;
else
propertyTypeTemplate = (TypeTemplate<?, T>) TypeFactory.resolveType(propertyType);
rv.offer(new PropertyFacade(property, propertyTypeTemplate, this));
}
for (var superType : hierarchy)
superType.template.postInit(() -> {
for (var inheritedProperty : superType.template.properties)
rv.offer(inheritedProperty);
});
return rv;
}
private Iterable<MethodFacade<? super T, ?>> initializeMethods() {
Queue<MethodFacade<? super T, ?>> rv = new ArrayDeque<>();
if (!isNative()) //
for (var method : annotatedElement.getDeclaredMethods()) {
if (method.isSynthetic())
continue; // skip lambdas
TypeTemplate<?, ?> returnType;
if (method.getReturnType() == annotatedElement)
returnType = this;
else
returnType = TypeFactory.resolveType(method.getGenericReturnType());
rv.offer(new MethodFacade<>(method, returnType, this));
}
for (var superType : hierarchy)
superType.template.postInit(() -> {
for (var inheritedMethod : superType.template.methods)
rv.offer(inheritedMethod);
});
return rv;
}
private void doSealHierarchy(Iterable<? extends IuType<?, ? super T>> hierarchy) {
Map<Class<?>, TypeFacade<?, ? super T>> hierarchyByErasure = new LinkedHashMap<>();
for (var superType : hierarchy) {
var templateReference = superType.reference();
TypeTemplate<?, ? super T> superTypeTemplate;
if (templateReference == null) {
superTypeTemplate = (TypeTemplate<?, ? super T>) superType;
// common case: direct generalization of raw class
hierarchyByErasure.put(superType.erasedClass(),
new TypeFacade<>(superTypeTemplate, this, IuReferenceKind.SUPER));
continue;
}
else // exception case: inherited generalization ...
superTypeTemplate = ((TypeFacade<?, ? super T>) superType).template;
// ... by type erasure, same as direct
var erasedReferrerClass = ((IuType<?, ?>) templateReference.referrer()).erasedClass();
if (erasedReferrerClass == annotatedElement) {
hierarchyByErasure.put(superType.erasedClass(),
new TypeFacade<>(superTypeTemplate, this, IuReferenceKind.SUPER));
continue;
}
// ... via superclass or interface, look up unsealed reference by erasure
final var referrer = Objects.requireNonNull(hierarchyByErasure.get(erasedReferrerClass));
final var superTypeFacade = new TypeFacade<>(superTypeTemplate, referrer, IuReferenceKind.SUPER);
hierarchyByErasure.put(superTypeFacade.erasedClass(), superTypeFacade);
}
this.hierarchy = hierarchyByErasure.values();
if (type instanceof ParameterizedType parameterizedType) {
final var actualTypeArguments = parameterizedType.getActualTypeArguments();
final var typeVariables = annotatedElement.getTypeParameters();
final var length = actualTypeArguments.length;
assert typeVariables.length == length; // enforced by javac
for (var i = 0; i < length; i++)
parameterizedElement.apply(this, typeVariables[i], actualTypeArguments[i]);
}
fields = initializeFields();
properties = initializeProperties();
methods = initializeMethods();
parameterizedElement.seal(annotatedElement, this);
super.seal();
}
/**
* Unsupported, use {@link #sealHierarchy(Iterable)} to provide hierarchy when
* sealing.
*
* @throws UnsupportedOperationException when invoked
*/
@Override
final void seal() throws UnsupportedOperationException {
throw new UnsupportedOperationException("use sealHierarchy() only with TypeTemplate");
}
private boolean isStatic() {
final var erased = erasedClass();
if (erased.isInterface() || erased.isRecord() || erased.isEnum())
return true;
final var mod = annotatedElement.getModifiers();
return (mod | Modifier.STATIC) == mod;
}
/**
* Seals {@link #hierarchy()} and resolves <strong>inherited elements</strong>.
*
* @param hierarchy Resolved type hierarchy
*/
void sealHierarchy(Iterable<? extends IuType<?, ? super T>> hierarchy) {
if (declaringType == null || isStatic())
doSealHierarchy(hierarchy);
else
declaringType.template.postInit(() -> doSealHierarchy(hierarchy));
}
@Override
public Runnable subscribe(InstanceReference<T> instanceReference) {
instanceReferences.accept(instanceReference);
return () -> instanceReferences.clear(instanceReference);
}
@Override
public void observe(T instance) {
instanceReferences.visit(listener -> {
if (listener != null)
listener.accept(instance);
return null;
});
for (final var method : annotatedMethods(PostConstruct.class))
IuException.unchecked(() -> method.exec(instance));
}
@Override
public void destroy(T instance) {
Throwable e = null;
for (final var method : annotatedMethods(PreDestroy.class))
e = IuException.suppress(e, () -> IuException.checkedInvocation(() -> {
method.exec(instance);
return null;
}));
e = IuException.suppress(e, () -> instanceReferences.visit(listener -> {
if (listener != null)
listener.clear(instance);
return null;
}));
if (e != null)
throw IuException.unchecked(e);
}
@Override
public Map<String, TypeFacade<?, ?>> typeParameters() {
checkSealed();
return parameterizedElement.typeParameters();
}
@Override
public String name() {
return annotatedElement.getName();
}
@Override
public IuTypeReference<T, ?> reference() {
return null;
}
@Override
public Type deref() {
return type;
}
@Override
public IuType<D, T> erase() {
return erasedType;
}
@Override
public Class<T> erasedClass() {
return annotatedElement;
}
@Override
public IuType<?, ? super T> referTo(Type referentType) {
return TypeUtils.referTo(this, hierarchy(), referentType);
}
@Override
public Iterable<TypeFacade<T, ?>> enclosedTypes() {
if (enclosedTypes == null)
initializeEnclosedTypes();
return enclosedTypes;
}
@Override
public Iterable<? extends IuConstructor<T>> constructors() {
return constructors;
}
@Override
public Iterable<TypeFacade<?, ? super T>> hierarchy() {
if (hierarchy == null)
throw new IllegalStateException("hierarchy not sealed");
return hierarchy;
}
@Override
@SuppressWarnings("unchecked")
public <F> FieldFacade<? super T, F> field(String name) {
return (FieldFacade<? super T, F>) IuType.super.field(name);
}
@Override
public Iterable<FieldFacade<? super T, ?>> fields() {
if (fields == null)
throw new IllegalStateException("fields not sealed");
return fields;
}
@Override
@SuppressWarnings("unchecked")
public <P> PropertyFacade<? super T, P> property(String name) {
return (PropertyFacade<? super T, P>) IuType.super.property(name);
}
@Override
public Iterable<PropertyFacade<? super T, ?>> properties() {
if (properties == null)
throw new IllegalStateException("properties not sealed");
return properties;
}
@Override
public Iterable<MethodFacade<? super T, ?>> methods() {
if (methods == null)
throw new IllegalStateException("methods not sealed");
return methods;
}
@Override
public String toString() {
return "IuType[" + TypeUtils.printType(deref()) + ']';
}
}