IuType.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.type;
import java.lang.annotation.Annotation;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.WeakHashMap;
import edu.iu.IuIterable;
import edu.iu.type.spi.TypeImplementation;
/**
* Facade interface for a generic type.
*
* <p>
* {@link IuType} is sterotyped as a <strong>hash key</strong>, so may be used
* as the key value with {@link WeakHashMap} to build type-level extensions from
* specific generic type scenarios. For example IuType has a 1:1 relationship
* with Class, but a separate 1:1 with a Classes referred to via specific
* {@link TypeVariable}, {@link ParameterizedType}, {@link GenericArrayType}, or
* {@link WildcardType}.
* </p>
*
* <p>
* The <strong>hash key stereotype</strong> is implemented by {@link #of(Class)}
* and {@link #of(Type)}, and is only implied by implementations provided by
* those methods.
* </p>
*
* <h2>Private Erasure</h2>
* <p>
* This interface exposes all declared field and method members from all types
* in decorated type's hierarchy in additional to those declared directly on its
* type erasure. That includes private members of superclasses. The purpose of
* this utility is to find fields and bean properties on well-formed application
* classes that may or may not be annotated as container-managed associations.
* The container therefore uses {@code IuType} to access privately scoped
* members and populate those associations.
* </p>
* <p>
* It is the application developer's responsibility to ensure that private
* members only shadow same-named private members in a super class when it is
* intended for the subclass to use its member instead of any inherited
* <strong>shadowed</strong> member of the same name. For example, when
* using @AroundInvoke to define interceptor methods intended to be inherited
* from a superclass, use a name reasonably unique to the declaring class. This
* scenario mirrors method override behavior, but fields may be shadowed and
* continue to exist as separate private fields with the same name but
* potentially different type and value.
* </p>
* <p>
* Private erasure is relevant for deserialization, remote service discovery,
* and method invocation. Other scenarios, i.e., dependency injection,
* <em>should</em> iterate all members unless a specific name is provided.
* </p>
*
* @param <D> declaring type, nullable
* @param <T> described generic type
*/
public interface IuType<D, T> extends IuNamedElement<D>, IuParameterizedElement {
/**
* Resolves a type introspection facade for a generic type.
*
* @param type generic type
* @return type introspection facade
*/
static IuType<?, ?> of(Type type) {
return TypeImplementation.PROVIDER.resolveType(type);
}
/**
* Resolves a type introspection facade for a class.
*
* <p>
* An introspection facade returned by this method <em>must</em>:
* </p>
* <ul>
* <li>Be {@link Modifier#FINAL final} and immutable.</li>
* <li>Have 1:1 parity with {@link Class} instances, such that
* {@code IuType.of(MyClass.class) == IuType.of(MyClass.class)} returns for all
* classes.</li>
* </ul>
*
* <p>
* All use of this method and subsequent type introspection lookups
* <em>must</em> thread-safe.
* </p>
*
* @param <T> type
* @param rawClass type
* @return type introspection facade
*/
@SuppressWarnings("unchecked")
static <T> IuType<?, T> of(Class<T> rawClass) {
return (IuType<?, T>) of((Type) rawClass);
}
/**
* Gets the reference used to obtain this type.
*
* @return type reference
*/
IuTypeReference<T, ?> reference();
/**
* Gets the generic type.
*
* @return generic type
*/
Type deref();
/**
* Get the type erasure class.
*
* <p>
* Shorthand for {@link #erase()}.{@link #deref()}
* </p>
*
* @return type erasure class
* @see #erase()
* @see <a href=
* "https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.6">JLS
* 21 Section 4.4: Type Erasure</a>
*/
@SuppressWarnings("unchecked")
default Class<T> erasedClass() {
return (Class<T>) erase().deref();
}
/**
* Returns the <a href=
* "https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html">autobox</a>
* equivalent
*
* @return the object version related to a primitive type, or the class passed
* in as-is if not primitive
*/
@SuppressWarnings("unchecked")
default Class<T> autoboxClass() {
var potentiallyPrimitive = erasedClass();
if (Boolean.TYPE.equals(potentiallyPrimitive))
return (Class<T>) Boolean.class;
else if (Character.TYPE.equals(potentiallyPrimitive))
return (Class<T>) Character.class;
else if (Byte.TYPE.equals(potentiallyPrimitive))
return (Class<T>) Byte.class;
else if (Short.TYPE.equals(potentiallyPrimitive))
return (Class<T>) Short.class;
else if (Integer.TYPE.equals(potentiallyPrimitive))
return (Class<T>) Integer.class;
else if (Long.TYPE.equals(potentiallyPrimitive))
return (Class<T>) Long.class;
else if (Float.TYPE.equals(potentiallyPrimitive))
return (Class<T>) Float.class;
else if (Double.TYPE.equals(potentiallyPrimitive))
return (Class<T>) Double.class;
else if (Void.TYPE.equals(potentiallyPrimitive))
return (Class<T>) Void.class;
else
return potentiallyPrimitive;
}
/**
* Returns the default value for an object or primitive type.
*
* @return The default value that would be assigned to a field of described
* primitive type if declared without an initializer; null if the
* described time is not primitive.
*/
@SuppressWarnings("unchecked")
default T autoboxDefault() {
var potentiallyPrimitive = erasedClass();
if (Boolean.TYPE.equals(potentiallyPrimitive))
return (T) Boolean.FALSE;
else if (Character.TYPE.equals(potentiallyPrimitive))
return (T) Character.valueOf('\0');
else if (Byte.TYPE.equals(potentiallyPrimitive))
return (T) Byte.valueOf((byte) 0);
else if (Short.TYPE.equals(potentiallyPrimitive))
return (T) Short.valueOf((short) 0);
else if (Integer.TYPE.equals(potentiallyPrimitive))
return (T) Integer.valueOf(0);
else if (Long.TYPE.equals(potentiallyPrimitive))
return (T) Long.valueOf(0L);
else if (Float.TYPE.equals(potentiallyPrimitive))
return (T) Float.valueOf(0.0f);
else if (Double.TYPE.equals(potentiallyPrimitive))
return (T) Double.valueOf(0.0);
else
return null;
}
/**
* Gets the {@link IuReferenceKind#ERASURE erased} facade, which describing the
* {@link Class} representing the <a href=
* "https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.6">erasure</a>
* of the generic type.
*
* <p>
* The {@link #deref()} of the erased facade <em>must</em> return a
* {@link Class}.
* </p>
*
* @return erased type facade
* @see <a href=
* "https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.6">JLS
* 21 Section 4.4: Type Erasure</a>
*/
IuType<D, T> erase();
/**
* Gets a type-enforced facade for a specific sub-type of the described type.
*
* @param subclass subclass of the described type
* @param <S> sub-type
* @return this
* @throws ClassCastException If the type does not erase to a subclass
*/
@SuppressWarnings("unchecked")
default <S> IuType<D, ? extends S> sub(Class<S> subclass) throws ClassCastException {
erasedClass().asSubclass(subclass);
return (IuType<D, ? extends S>) this;
}
/**
* Iterates the type hierarchy, from most specific to least specific.
*
* <ol>
* <li>All {@link Class#getGenericInterfaces()}</li>
* <li>{@link Class#getGenericSuperclass()}</li>
* <li>Iterate {@link IuType#hierarchy()} until {@link Object} is reached</li>
* </ol>
*
* <p>
* This type described by this facade is not included. {@link Object} is always
* the last element.
* </p>
*
* @return inherited and extended types
*/
Iterable<? extends IuType<?, ? super T>> hierarchy();
/**
* Refers to a type in the the described type's hierarchy.
*
* <p>
* When the referent type declares type parameters, the resolved generic types
* associated with those parameters are described by the returned facade.
* </p>
*
* @param referentType type to refer to
* @return referent facade
*/
IuType<?, ? super T> referTo(Type referentType);
/**
* Gets enclosed types.
*
* @return enclosed types
*/
Iterable<? extends IuType<?, ?>> enclosedTypes();
/**
* Gets all constructors defined by this type.
*
* @return constructors
*/
Iterable<? extends IuConstructor<T>> constructors();
/**
* Gets a constructor defined by this type.
*
* @param parameterTypes parameter types
* @return constructor
*/
default IuConstructor<T> constructor(Type... parameterTypes) {
var hash = IuExecutableKey.hashCode(null, parameterTypes);
for (var constructor : constructors()) {
var constructorKey = constructor.getKey();
if (hash == constructorKey.hashCode() && constructorKey.equals(null, parameterTypes))
return constructor;
}
throw new IllegalArgumentException(this + " missing constructor " + IuExecutableKey.of(null, parameterTypes));
}
/**
* Gets a constructor declared by this type.
*
* @param parameterTypes parameter types
* @return constructor
*/
default IuConstructor<T> constructor(Iterable<IuType<?, ?>> parameterTypes) {
var hash = IuExecutableKey.hashCode(null, parameterTypes);
for (var constructor : constructors()) {
var constructorKey = constructor.getKey();
if (hash == constructorKey.hashCode() && constructorKey.equals(null, parameterTypes))
return constructor;
}
throw new IllegalArgumentException(this + " missing constructor " + IuExecutableKey.of(null, parameterTypes));
}
/**
* Scans constructors for those annotated with a specific annotation type.
*
* @param annotationType annotation type to filter by
* @return {@link #constructors()}, filtered by annotation type
*/
default Iterable<? extends IuConstructor<T>> annotatedConstructors(Class<? extends Annotation> annotationType) {
return IuIterable.filter(constructors(), c -> c.hasAnnotation(annotationType));
}
/**
* Gets all fields defined by this type, followed by all fields defined by all
* types in this type's hierarchy, in {@link #hierarchy()} order.
*
* @return fields declared by this type and its hierarchy, in this followed by
* {@link #hierarchy()} order
*/
Iterable<? extends IuField<? super T, ?>> fields();
/**
* Gets a field declared by this type.
*
* <p>
* When a private field has the same name as a different field declared by a
* super class, the "inherited" field is shadowed by this method. To retrieve
* all fields, including those shadowed by a superclass, use {@link #fields()}.
* </p>
*
* @param <F> field type
* @param name field name
* @return field
*/
@SuppressWarnings("unchecked")
default <F> IuField<? super T, F> field(String name) {
for (var field : fields())
if (name.equals(field.name()))
return (IuField<? super T, F>) field;
throw new IllegalArgumentException(this + " missing field " + name);
}
/**
* Scans fields for those annotated with a specific annotation type.
*
* @param annotationType annotation type to filter by
* @return {@link #fields()}, filtered by annotation type
*/
default Iterable<? extends IuField<? super T, ?>> annotatedFields(Class<? extends Annotation> annotationType) {
return IuIterable.filter(fields(), f -> f.hasAnnotation(annotationType));
}
/**
* Gets all methods defined by this type.
*
* <p>
* The result {@link Iterable iterates} all methods declared on all classes in
* the type erasure's hierarchy with private erasure for duplicately defined
* methods.
* </p>
*
* @return methods
*/
Iterable<? extends IuMethod<? super T, ?>> methods();
/**
* Gets a method defined by this type.
*
* @param <R> return type
* @param name method name
* @param parameterTypes parameter types
* @return method
*/
@SuppressWarnings("unchecked")
default <R> IuMethod<? super T, R> method(String name, Type... parameterTypes) {
final var hash = IuExecutableKey.hashCode(name, parameterTypes);
final var methods = methods();
for (var method : methods) {
var methodKey = method.getKey();
if (hash == methodKey.hashCode() && methodKey.equals(name, parameterTypes))
return (IuMethod<? super T, R>) method;
}
throw new IllegalArgumentException(
this + " missing method " + IuExecutableKey.of(name, parameterTypes) + "; " + methods);
}
/**
* Gets a method declared by this type.
*
* @param <R> return type
* @param name method name
* @param parameterTypes parameter types
* @return method
*/
@SuppressWarnings("unchecked")
default <R> IuMethod<? super T, R> method(String name, Iterable<IuType<?, ?>> parameterTypes) {
final var hash = IuExecutableKey.hashCode(name, parameterTypes);
final var methods = methods();
for (var method : methods) {
var methodKey = method.getKey();
if (hash == methodKey.hashCode() && methodKey.equals(name, parameterTypes))
return (IuMethod<? super T, R>) method;
}
throw new IllegalArgumentException(
this + " missing method " + IuExecutableKey.of(name, parameterTypes) + "; " + methods);
}
/**
* Scans methods for those annotated with a specific annotation type.
*
* @param annotationType annotation type to filter by
* @return {@link #methods()}, filtered by annotation type
*/
default Iterable<? extends IuMethod<? super T, ?>> annotatedMethods(Class<? extends Annotation> annotationType) {
return IuIterable.filter(methods(), f -> f.hasAnnotation(annotationType));
}
/**
* Gets all properties defined by this type, followed by all properties defined
* by all types in this type's hierarchy, in {@link #hierarchy()} order.
*
* @return properties declared by this type and its hierarchy, in this followed
* by {@link #hierarchy()} order
*/
Iterable<? extends IuProperty<? super T, ?>> properties();
/**
* Gets a property declared by this type.
*
* @param <P> property type
* @param name property name
* @return property
*/
@SuppressWarnings("unchecked")
default <P> IuProperty<? super T, P> property(String name) {
for (var property : properties())
if (name.equals(property.name()))
return (IuProperty<? super T, P>) property;
throw new IllegalArgumentException(this + " missing property " + name);
}
/**
* Scans properties for those annotated with a specific annotation type.
*
* @param annotationType annotation type to filter by
* @return {@link #properties()}, filtered by annotation type
*/
default Iterable<? extends IuProperty<? super T, ?>> annotatedProperties(
Class<? extends Annotation> annotationType) {
return IuIterable.filter(properties(), f -> f.hasAnnotation(annotationType));
}
/**
* Observes a new instance.
*
* <p>
* Observing an instance registers it with the implementation module as an
* available target for type introspection, for example, for resource binding.
* Implementors of {@link InstanceReference} may use
* {@link #subscribe(InstanceReference)} to be notified when new instances are
* observed.
* </p>
*
* <p>
* Once all {@link InstanceReference}s have been notified, all methods annotated
* by {@literal @}PostConstruct will be invoked on the instance.
* </p>
*
* <p>
* Observing an instance that is already observed has no effect, nor does
* observing an instance of a type that has no subscribers. All instances
* provided via {@link IuConstructor#exec(Object...)} are observed
* automatically. This method is a no-op for those instances, however it is up
* to implementors to ensure {@literal @}PostConstruct methods do not result in
* errors or repeat initialization steps when invoked repeatedly.
* </p>
*
* @param instance to observe
*/
void observe(T instance);
/**
* Destroys an instance.
*
* <p>
* Invokes all {@literal @}PreDestroy methods on an instance. If the instance
* was {@link #observe(Object) observed}, its state will be reverted and no
* futures actions will be taken on it.
* </p>
*
* <p>
* This method may be invoked to close the lifecycle of instances created via
* {@link IuConstructor#exec(Object...)}.
* </p>
*
* <p>
* Destroying an instance that is already destroyed <em>should</em> have no
* effect, but it is up to implementors to ensure {@literal @}PreDestroy methods
* do not result in errors or other side-effects when invoked repeatedly.
* </p>
*
* @param instance to destroy
*/
void destroy(T instance);
/**
* Subscribes a new instance reference.
*
* @param instanceReference will accept all {@link #observe(Object) observed}
* instances until unsubscribed.
* @return thunk for unsubscribing the reference
*/
Runnable subscribe(InstanceReference<T> instanceReference);
}