com.fitbur.mockito.bytebuddy.description.annotation.AnnotationDescription Maven / Gradle / Ivy
package com.fitbur.mockito.bytebuddy.description.annotation;
import com.fitbur.mockito.bytebuddy.description.enumeration.EnumerationDescription;
import com.fitbur.mockito.bytebuddy.description.method.MethodDescription;
import com.fitbur.mockito.bytebuddy.description.method.MethodList;
import com.fitbur.mockito.bytebuddy.description.type.TypeDescription;
import com.fitbur.mockito.bytebuddy.description.type.TypeList;
import com.fitbur.mockito.bytebuddy.utility.AccessAction;
import com.fitbur.mockito.bytebuddy.utility.PropertyDispatcher;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.security.AccessController;
import java.util.*;
import static com.fitbur.mockito.bytebuddy.matcher.ElementMatchers.named;
/**
* An annotation description describes {@link java.lang.annotation.Annotation} meta data of a class without this class
* being required to be loaded. All values of an annotation are therefore represented in unloaded state:
*
* - {@link java.lang.Class} instances are represented as {@link TypeDescription}s.
* - {@link java.lang.Enum} instances are represented as
* {@link com.fitbur.mockito.bytebuddy.description.enumeration.EnumerationDescription}s.
* - {@link java.lang.annotation.Annotation}s are described as
* {@link AnnotationDescription}s.
* - All primitive types are represented as their wrapper types.
*
* An annotation can however be loaded in order to access unwrapped values. This will cause a loading of the classes
* of these values.
*/
public interface AnnotationDescription {
/**
* Indicates an inexistent annotation in a type-safe manner.
*/
AnnotationDescription.Loadable> UNDEFINED = null;
/**
* Returns the value of the given method for this annotation value. Note that all return values are wrapped as
* described by {@link AnnotationDescription}.
*
* @param methodDescription The method for the value to be requested.
* @return The value for the given method.
*/
Object getValue(MethodDescription.InDefinedShape methodDescription);
/**
* Returns the value of the given method for this annotation value and performs a casting to the given value. Note
* that all return values are wrapped described by
* {@link AnnotationDescription}.
*
* @param methodDescription The method for the value to be requested.
* @param type The type to which the returned value should be casted.
* @param The given type of the return value.
* @return The value for the given method casted to {@code type}.
*/
T getValue(MethodDescription.InDefinedShape methodDescription, Class type);
/**
* Returns a description of the annotation type of this annotation.
*
* @return A description of the annotation type of this annotation.
*/
TypeDescription getAnnotationType();
/**
* Links this annotation description to a given annotation type such that it can be loaded. This does not cause
* the values of this annotation to be loaded.
*
* @param annotationType The loaded annotation type of this annotation description.
* @param The type of the annotation.
* @return A loadable version of this annotation description.
*/
Loadable prepare(Class annotationType);
/**
* Returns this annotation's retention policy.
*
* @return This annotation's retention policy.
*/
RetentionPolicy getRetention();
/**
* Returns a set of all {@link ElementType}s that can declare this annotation.
*
* @return A set of all element types that can declare this annotation.
*/
Set getElementTypes();
/**
* Checks if this annotation is inherited.
*
* @return {@code true} if this annotation is inherited.
* @see Inherited
*/
boolean isInherited();
/**
* Checks if this annotation is documented.
*
* @return {@code true} if this annotation is documented.
* @see Documented
*/
boolean isDocumented();
/**
* A description of an annotation's value.
*
* @param The type of the annotation's value when it is not loaded.
* @param The type of the annotation's value when it is loaded.
*/
interface AnnotationValue {
/**
* Resolves the unloaded value of this annotation.
*
* @return The unloaded value of this annotation.
*/
T resolve();
/**
* Returns the loaded value of this annotation.
*
* @param classLoader The class loader for loading this value.
* @return The loaded value of this annotation.
* @throws ClassNotFoundException If a type that represents a loaded value cannot be found.
*/
Loaded load(ClassLoader classLoader) throws ClassNotFoundException;
/**
* A loaded variant of an {@link AnnotationValue}. While
* implementations of this value are required to be processed successfully by a
* {@link java.lang.ClassLoader} they might still be unresolved. Typical errors on loading an annotation
* value are:
*
* - {@link java.lang.annotation.IncompleteAnnotationException}: An annotation does not define a value
* even though no default value for a property is provided.
* - {@link java.lang.EnumConstantNotPresentException}: An annotation defines an unknown value for
* a known enumeration.
* - {@link java.lang.annotation.AnnotationTypeMismatchException}: An annotation property is not
* of the expected type.
*
* Implementations of this interface must implement methods for {@link Object#hashCode()} and
* {@link Object#toString()} that resemble those used for the annotation values of an actual
* {@link java.lang.annotation.Annotation} implementation. Also, instances must implement
* {@link java.lang.Object#equals(Object)} to return {@code true} for other instances of
* this interface that represent the same annotation value.
*
* @param The type of the loaded value of this annotation.
*/
interface Loaded {
/**
* Returns the state of the represented loaded annotation value.
*
* @return The state represented by this instance.
*/
State getState();
/**
* Resolves the value to the actual value of an annotation. Calling this method might throw a runtime
* exception if this value is either not defined or not resolved.
*
* @return The actual annotation value represented by this instance.
*/
U resolve();
/**
* Represents the state of a {@link Loaded} annotation property.
*/
enum State {
/**
* A non-defined annotation value describes an annotation property which is missing such that
* an {@link java.lang.annotation.IncompleteAnnotationException} would be thrown.
*/
NON_DEFINED,
/**
* A non-resolved annotation value describes an annotation property which does not represent a
* valid value but an exceptional state.
*/
NON_RESOLVED,
/**
* A resolved annotation value describes an annotation property with an actual value.
*/
RESOLVED;
/**
* Returns {@code true} if the related annotation value is defined, i.e. either represents
* an actual value or an exceptional state.
*
* @return {@code true} if the related annotation value is defined.
*/
public boolean isDefined() {
return this != NON_DEFINED;
}
/**
* Returns {@code true} if the related annotation value is resolved, i.e. represents an actual
* value.
*
* @return {@code true} if the related annotation value is resolved.
*/
public boolean isResolved() {
return this == RESOLVED;
}
@Override
public String toString() {
return "AnnotationDescription.AnnotationValue.Loaded.State." + name();
}
}
}
/**
* Represents a primitive value, a {@link java.lang.String} or an array of the latter types.
*
* @param The type where primitive values are represented by their boxed type.
*/
class Trivial implements AnnotationValue {
/**
* The represented value.
*/
private final U value;
/**
* A property dispatcher for the given value.
*/
private final PropertyDispatcher propertyDispatcher;
/**
* Creates a new representation of a trivial annotation value.
*
* @param value The value to represent.
*/
public Trivial(U value) {
this.value = value;
propertyDispatcher = PropertyDispatcher.of(value.getClass());
}
@Override
public U resolve() {
return value;
}
@Override
public AnnotationValue.Loaded load(ClassLoader classLoader) {
return new Loaded(value, propertyDispatcher);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& propertyDispatcher.equals(value, ((Trivial) other).value);
}
@Override
public int hashCode() {
return propertyDispatcher.hashCode(value);
}
@Override
public String toString() {
return "AnnotationDescription.AnnotationValue.Trivial{" +
"value=" + value +
", propertyDispatcher=" + propertyDispatcher +
'}';
}
/**
* Represents a trivial loaded value.
*
* @param The annotation properties type.
*/
public static class Loaded implements AnnotationValue.Loaded {
/**
* The represented value.
*/
private final V value;
/**
* The property dispatcher for computing this value's hash code, string and equals implementation.
*/
private final PropertyDispatcher propertyDispatcher;
/**
* Creates a new trivial loaded annotation value representation.
*
* @param value The represented value.
* @param propertyDispatcher The property dispatcher for computing this value's hash
* code, string and equals implementation.
*/
public Loaded(V value, PropertyDispatcher propertyDispatcher) {
this.value = value;
this.propertyDispatcher = propertyDispatcher;
}
@Override
public State getState() {
return State.RESOLVED;
}
@Override
public V resolve() {
return propertyDispatcher.conditionalClone(value);
}
@Override
public int hashCode() {
return propertyDispatcher.hashCode(value);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof AnnotationValue.Loaded>)) return false;
AnnotationValue.Loaded> loadedOther = (AnnotationValue.Loaded>) other;
return loadedOther.getState().isResolved() && propertyDispatcher.equals(value, loadedOther.resolve());
}
@Override
public String toString() {
return propertyDispatcher.toString(value);
}
}
}
/**
* A description of an {@link java.lang.annotation.Annotation} as a value of another annotation.
*
* @param The type of the annotation.
*/
class ForAnnotation implements AnnotationValue {
/**
* The annotation description that this value represents.
*/
private final AnnotationDescription annotationDescription;
/**
* Creates a new annotation value for a given annotation description.
*
* @param annotationDescription The annotation description that this value represents.
*/
public ForAnnotation(AnnotationDescription annotationDescription) {
this.annotationDescription = annotationDescription;
}
/**
* Creates an annotation value instance for describing the given annotation type and values.
*
* @param annotationType The annotation type.
* @param annotationValues The values of the annotation.
* @param The type of the annotation.
* @return An annotation value representing the given annotation.
*/
public static AnnotationValue of(TypeDescription annotationType,
Map> annotationValues) {
return new ForAnnotation(new AnnotationDescription.Latent(annotationType, annotationValues));
}
@Override
public AnnotationDescription resolve() {
return annotationDescription;
}
@Override
public AnnotationValue.Loaded load(ClassLoader classLoader) throws ClassNotFoundException {
@SuppressWarnings("unchecked")
Class annotationType = (Class) classLoader.loadClass(annotationDescription.getAnnotationType().getName());
return new Loaded(annotationDescription.prepare(annotationType).load(classLoader));
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
ForAnnotation that = (ForAnnotation) other;
return annotationDescription.equals(that.annotationDescription);
}
@Override
public int hashCode() {
return annotationDescription.hashCode();
}
@Override
public String toString() {
return "AnnotationDescription.AnnotationValue.ForAnnotation{" +
"annotationDescription=" + annotationDescription +
'}';
}
/**
* A loaded version of the described annotation.
*
* @param The annotation type.
*/
public static class Loaded implements AnnotationValue.Loaded {
/**
* The loaded version of the represented annotation.
*/
private final V annotation;
/**
* Creates a representation of a loaded annotation.
*
* @param annotation The represented annotation.
*/
public Loaded(V annotation) {
this.annotation = annotation;
}
@Override
public State getState() {
return State.RESOLVED;
}
@Override
public V resolve() {
return annotation;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof AnnotationValue.Loaded>)) return false;
AnnotationValue.Loaded> loadedOther = (AnnotationValue.Loaded>) other;
return loadedOther.getState().isResolved() && annotation.equals(loadedOther.resolve());
}
@Override
public int hashCode() {
return annotation.hashCode();
}
@Override
public String toString() {
return annotation.toString();
}
}
/**
*
* Represents an annotation value which was attempted to ba loaded by a type that does not represent
* an annotation value.
*
*
* Note: Neither of {@link Object#hashCode()}, {@link Object#toString()} and
* {@link java.lang.Object#equals(Object)} are implemented specifically what resembles the way
* such exceptional states are represented in the Open JDK's annotation implementations.
*
*/
public static class IncompatibleRuntimeType implements AnnotationValue.Loaded {
/**
* The incompatible runtime type which is not an annotation type.
*/
private final Class> incompatibleType;
/**
* Creates a new representation for an annotation with an incompatible runtime type.
*
* @param incompatibleType The incompatible runtime type which is not an annotation type.
*/
public IncompatibleRuntimeType(Class> incompatibleType) {
this.incompatibleType = incompatibleType;
}
@Override
public State getState() {
return State.NON_RESOLVED;
}
@Override
public Annotation resolve() {
throw new IncompatibleClassChangeError("Not an annotation type: " + incompatibleType.toString());
}
/* does intentionally not implement hashCode, equals and toString */
}
}
/**
* A description of an {@link java.lang.Enum} as a value of an annotation.
*
* @param The type of the enumeration.
*/
class ForEnumeration> implements AnnotationValue {
/**
* The enumeration that is represented.
*/
private final EnumerationDescription enumerationDescription;
/**
* Creates a new description of an annotation value for a given enumeration.
*
* @param enumerationDescription The enumeration that is to be represented.
*/
public ForEnumeration(EnumerationDescription enumerationDescription) {
this.enumerationDescription = enumerationDescription;
}
/**
* Creates a new annotation value for the given enumeration description.
*
* @param value The value to represent.
* @param The type of the represented enumeration.
* @return An annotation value that describes the given enumeration.
*/
public static > AnnotationValue of(EnumerationDescription value) {
return new ForEnumeration(value);
}
@Override
public EnumerationDescription resolve() {
return enumerationDescription;
}
@Override
public AnnotationValue.Loaded load(ClassLoader classLoader) throws ClassNotFoundException {
@SuppressWarnings("unchecked")
Class enumerationType = (Class) classLoader.loadClass(enumerationDescription.getEnumerationType().getName());
return new Loaded(enumerationDescription.load(enumerationType));
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
ForEnumeration that = (ForEnumeration) other;
return enumerationDescription.equals(that.enumerationDescription);
}
@Override
public int hashCode() {
return enumerationDescription.hashCode();
}
@Override
public String toString() {
return "AnnotationDescription.AnnotationValue.ForEnumeration{" +
"enumerationDescription=" + enumerationDescription +
'}';
}
/**
* A loaded representation of an enumeration value.
*
* @param The type of the represented enumeration.
*/
public static class Loaded> implements AnnotationValue.Loaded {
/**
* The represented enumeration.
*/
private final V enumeration;
/**
* Creates a loaded version of an enumeration description.
*
* @param enumeration The represented enumeration.
*/
public Loaded(V enumeration) {
this.enumeration = enumeration;
}
@Override
public State getState() {
return State.RESOLVED;
}
@Override
public V resolve() {
return enumeration;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof AnnotationValue.Loaded>)) return false;
AnnotationValue.Loaded> loadedOther = (AnnotationValue.Loaded>) other;
return loadedOther.getState().isResolved() && enumeration.equals(loadedOther.resolve());
}
@Override
public int hashCode() {
return enumeration.hashCode();
}
@Override
public String toString() {
return enumeration.toString();
}
}
/**
*
* Represents an annotation's enumeration value for a constant that does not exist for the runtime
* enumeration type.
*
*
* Note: Neither of {@link Object#hashCode()}, {@link Object#toString()} and
* {@link java.lang.Object#equals(Object)} are implemented specifically what resembles the way
* such exceptional states are represented in the Open JDK's annotation implementations.
*
*/
public static class UnknownRuntimeEnumeration implements AnnotationValue.Loaded> {
/**
* The loaded enumeration type.
*/
private final Class extends Enum>> enumType;
/**
* The value for which no enumeration constant exists at runtime.
*/
private final String value;
/**
* Creates a new representation for an unknown enumeration constant of an annotation.
*
* @param enumType The loaded enumeration type.
* @param value The value for which no enumeration constant exists at runtime.
*/
public UnknownRuntimeEnumeration(Class extends Enum>> enumType, String value) {
this.enumType = enumType;
this.value = value;
}
@Override
public State getState() {
return State.NON_RESOLVED;
}
@Override
public Enum> resolve() {
throw new EnumConstantNotPresentException(enumType, value);
}
/* hashCode, equals and toString are intentionally not implemented */
}
/**
*
* Represents an annotation's enumeration value for a runtime type that is not an enumeration type.
*
*
* Note: Neither of {@link Object#hashCode()}, {@link Object#toString()} and
* {@link java.lang.Object#equals(Object)} are implemented specifically what resembles the way
* such exceptional states are represented in the Open JDK's annotation implementations.
*
*/
public static class IncompatibleRuntimeType implements AnnotationValue.Loaded> {
/**
* The runtime type which is not an enumeration type.
*/
private final Class> type;
/**
* Creates a new representation for an incompatible runtime type.
*
* @param type The runtime type which is not an enumeration type.
*/
public IncompatibleRuntimeType(Class> type) {
this.type = type;
}
@Override
public State getState() {
return State.NON_RESOLVED;
}
@Override
public Enum> resolve() {
throw new IncompatibleClassChangeError("Not an enumeration type: " + type.toString());
}
/* hashCode, equals and toString are intentionally not implemented */
}
}
/**
* A description of a {@link java.lang.Class} as a value of an annotation.
*
* @param The type of the {@link java.lang.Class} that is described.
*/
class ForType> implements AnnotationValue {
/**
* Indicates to a class loading process that class initializers are not required to be executed when loading a type.
*/
private static final boolean NO_INITIALIZATION = false;
/**
* A description of the represented type.
*/
private final TypeDescription typeDescription;
/**
* Creates a new annotation value that represents a type.
*
* @param typeDescription The represented type.
*/
public ForType(TypeDescription typeDescription) {
this.typeDescription = typeDescription;
}
/**
* Creates an annotation value for representing the given type.
*
* @param typeDescription The type to represent.
* @param The represented type.
* @return An annotation value that represents the given type.
*/
public static > AnnotationValue of(TypeDescription typeDescription) {
return new ForType(typeDescription);
}
@Override
public TypeDescription resolve() {
return typeDescription;
}
@Override
@SuppressWarnings("unchecked")
public AnnotationValue.Loaded load(ClassLoader classLoader) throws ClassNotFoundException {
return new Loaded((U) Class.forName(typeDescription.getName(), NO_INITIALIZATION, classLoader));
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
ForType forType = (ForType) other;
return typeDescription.equals(forType.typeDescription);
}
@Override
public int hashCode() {
return typeDescription.hashCode();
}
@Override
public String toString() {
return "AnnotationDescription.AnnotationValue.ForType{" +
"typeDescription=" + typeDescription +
'}';
}
/**
* A loaded annotation value for a given type.
*
* @param The represented type.
*/
protected static class Loaded> implements AnnotationValue.Loaded {
/**
* The represented type.
*/
private final U type;
/**
* Creates a new loaded annotation value for a given type.
*
* @param type The represented type.
*/
public Loaded(U type) {
this.type = type;
}
@Override
public State getState() {
return State.RESOLVED;
}
@Override
public U resolve() {
return type;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof AnnotationValue.Loaded>)) return false;
AnnotationValue.Loaded> loadedOther = (AnnotationValue.Loaded>) other;
return loadedOther.getState().isResolved() && type.equals(loadedOther.resolve());
}
@Override
public int hashCode() {
return type.hashCode();
}
@Override
public String toString() {
return type.toString();
}
}
}
/**
* Describes a complex array that is the value of an annotation. Complex arrays are arrays that might trigger the loading
* of user-defined types, i.e. {@link java.lang.Class}, {@link java.lang.annotation.Annotation} and {@link java.lang.Enum}
* instances.
*
* @param The component type of the annotation's value when it is not loaded.
* @param The component type of the annotation's value when it is loaded.
*/
class ForComplexArray implements AnnotationValue {
/**
* The component type for arrays containing unloaded versions of the annotation array's values.
*/
private final Class> unloadedComponentType;
/**
* A description of the component type when it is loaded.
*/
private final TypeDescription componentType;
/**
* A list of values of the array elements.
*/
private final List extends AnnotationValue, ?>> annotationValues;
/**
* Creates a new complex array.
*
* @param unloadedComponentType The component type for arrays containing unloaded versions of the annotation array's values.
* @param componentType A description of the component type when it is loaded.
* @param annotationValues A list of values of the array elements.
*/
protected ForComplexArray(Class> unloadedComponentType,
TypeDescription componentType,
List extends AnnotationValue, ?>> annotationValues) {
this.unloadedComponentType = unloadedComponentType;
this.componentType = componentType;
this.annotationValues = annotationValues;
}
/**
* Creates a new complex array of enumeration descriptions.
*
* @param enumerationType A description of the type of the enumeration.
* @param enumerationDescription An array of enumeration descriptions.
* @param The type of the enumeration.
* @return A description of the array of enumeration values.
*/
public static > AnnotationValue of(TypeDescription enumerationType,
EnumerationDescription[] enumerationDescription) {
List> values = new ArrayList>(enumerationDescription.length);
for (EnumerationDescription value : enumerationDescription) {
if (!value.getEnumerationType().equals(enumerationType)) {
throw new IllegalArgumentException(value + " is not of " + enumerationType);
}
values.add(ForEnumeration.of(value));
}
return new ForComplexArray(EnumerationDescription.class, enumerationType, values);
}
/**
* Creates a new complex array of annotation descriptions.
*
* @param annotationType A description of the type of the annotation.
* @param annotationDescription An array of annotation descriptions.
* @param The type of the annotation.
* @return A description of the array of enumeration values.
*/
public static AnnotationValue of(TypeDescription annotationType,
AnnotationDescription[] annotationDescription) {
List> values = new ArrayList>(annotationDescription.length);
for (AnnotationDescription value : annotationDescription) {
if (!value.getAnnotationType().equals(annotationType)) {
throw new IllegalArgumentException(value + " is not of " + annotationType);
}
values.add(new ForAnnotation(value));
}
return new ForComplexArray(AnnotationDescription.class, annotationType, values);
}
/**
* Creates a new complex array of annotation descriptions.
*
* @param typeDescription A description of the types contained in the array.
* @return A description of the array of enumeration values.
*/
@SuppressWarnings("unchecked")
public static AnnotationValue[]> of(TypeDescription[] typeDescription) {
List>> values = new ArrayList>>(typeDescription.length);
for (TypeDescription value : typeDescription) {
values.add((AnnotationValue) ForType.of(value));
}
return new ForComplexArray>(TypeDescription.class, TypeDescription.CLASS, values);
}
@Override
public U[] resolve() {
@SuppressWarnings("unchecked")
U[] value = (U[]) Array.newInstance(unloadedComponentType, annotationValues.size());
int index = 0;
for (AnnotationValue, ?> annotationValue : annotationValues) {
Array.set(value, index++, annotationValue.resolve());
}
return value;
}
@Override
@SuppressWarnings("unchecked")
public AnnotationValue.Loaded load(ClassLoader classLoader) throws ClassNotFoundException {
List> loadedValues = new ArrayList>(annotationValues.size());
for (AnnotationValue, ?> value : annotationValues) {
loadedValues.add(value.load(classLoader));
}
return new Loaded((Class) classLoader.loadClass(componentType.getName()), loadedValues);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
ForComplexArray that = (ForComplexArray) other;
return annotationValues.equals(that.annotationValues)
&& componentType.equals(that.componentType)
&& unloadedComponentType.equals(that.unloadedComponentType);
}
@Override
public int hashCode() {
int result = unloadedComponentType.hashCode();
result = 31 * result + componentType.hashCode();
result = 31 * result + annotationValues.hashCode();
return result;
}
@Override
public String toString() {
return "AnnotationDescription.AnnotationValue.ForComplexArra{" +
"unloadedComponentType=" + unloadedComponentType +
", componentType=" + componentType +
", annotationValues=" + annotationValues +
'}';
}
/**
* Represents a loaded complex array.
*
* @param The component type of the loaded array.
*/
protected static class Loaded implements AnnotationValue.Loaded {
/**
* The loaded component type of the array.
*/
private final Class componentType;
/**
* A list of loaded values that the represented array contains.
*/
private final List> values;
/**
* Creates a new loaded value representing a complex array.
*
* @param componentType The loaded component type of the array.
* @param values A list of loaded values that the represented array contains.
*/
protected Loaded(Class componentType, List> values) {
this.componentType = componentType;
this.values = values;
}
@Override
public State getState() {
for (AnnotationValue.Loaded> value : values) {
if (!value.getState().isResolved()) {
return State.NON_RESOLVED;
}
}
return State.RESOLVED;
}
@Override
public W[] resolve() {
@SuppressWarnings("unchecked")
W[] array = (W[]) Array.newInstance(componentType, values.size());
int index = 0;
for (AnnotationValue.Loaded> annotationValue : values) {
Array.set(array, index++, annotationValue.resolve());
}
return array;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof AnnotationValue.Loaded>)) return false;
AnnotationValue.Loaded> loadedOther = (AnnotationValue.Loaded>) other;
if (!loadedOther.getState().isResolved()) return false;
Object otherValue = loadedOther.resolve();
if (!(otherValue instanceof Object[])) return false;
Object[] otherArrayValue = (Object[]) otherValue;
if (values.size() != otherArrayValue.length) return false;
Iterator> iterator = values.iterator();
for (Object value : otherArrayValue) {
AnnotationValue.Loaded> self = iterator.next();
if (!self.getState().isResolved() || !self.resolve().equals(value)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int result = 1;
for (AnnotationValue.Loaded> value : values) {
result = 31 * result + value.hashCode();
}
return result;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("[");
for (AnnotationValue.Loaded> value : values) {
stringBuilder.append(value.toString());
}
return stringBuilder.append("]").toString();
}
}
}
}
/**
* An annotation description that is linked to a given loaded annotation type which allows its representation
* as a fully loaded instance.
*
* @param The annotation type.
*/
interface Loadable extends AnnotationDescription {
/**
* Loads this annotation description. This causes all classes referenced by the annotation value to be loaded.
* Without specifying a class loader, the annotation's class loader which was used to prepare this instance
* is used.
*
* @return A loaded version of this annotation description.
* @throws java.lang.ClassNotFoundException If any linked classes of the annotation cannot be loaded.
*/
S load() throws ClassNotFoundException;
/**
* Loads this annotation description. This causes all classes referenced by the annotation value to be loaded.
*
* @param classLoader The class loader to be used for loading the annotation's linked types.
* @return A loaded version of this annotation description.
* @throws java.lang.ClassNotFoundException If any linked classes of the annotation cannot be loaded.
*/
S load(ClassLoader classLoader) throws ClassNotFoundException;
/**
* Loads this annotation description. This causes all classes referenced by the annotation value to be loaded.
* Without specifying a class loader, the annotation's class loader which was used to prepare this instance
* is used. Any {@link java.lang.ClassNotFoundException} is wrapped in an {@link java.lang.IllegalStateException}.
*
* @return A loaded version of this annotation description.
*/
S loadSilent();
/**
* Loads this annotation description. This causes all classes referenced by the annotation value to be loaded.
* Any {@link java.lang.ClassNotFoundException} is wrapped in an {@link java.lang.IllegalStateException}.
*
* @param classLoader The class loader to be used for loading the annotation's linked types.
* @return A loaded version of this annotation description.
*/
S loadSilent(ClassLoader classLoader);
}
/**
* An {@link java.lang.reflect.InvocationHandler} for implementing annotations.
*
* @param The type of the handled annotation.
*/
class AnnotationInvocationHandler implements InvocationHandler {
/**
* The name of the {@link Object#hashCode()} method.
*/
private static final String HASH_CODE = "hashCode";
/**
* The name of the {@link Object#equals(Object)} method.
*/
private static final String EQUALS = "equals";
/**
* The name of the {@link Object#toString()} method.
*/
private static final String TO_STRING = "toString";
/**
* The loaded annotation type.
*/
private final Class extends Annotation> annotationType;
/**
* A sorted list of values of this annotation.
*/
private final LinkedHashMap> values;
/**
* Creates a new invocation handler.
*
* @param annotationType The loaded annotation type.
* @param values A sorted list of values of this annotation.
*/
protected AnnotationInvocationHandler(Class annotationType, LinkedHashMap> values) {
this.annotationType = annotationType;
this.values = values;
}
/**
* @param classLoader The class loader that should be used for loading the annotation's values.
* @param annotationType The annotation's type.
* @param values The values that the annotation contains.
* @param The type of the handled annotation.
* @return An appropriate invocation handler.
* @throws ClassNotFoundException If the class of an instance that is contained by this annotation could not be found.
*/
public static InvocationHandler of(ClassLoader classLoader,
Class annotationType,
Map> values)
throws ClassNotFoundException {
Method[] declaredMethod = annotationType.getDeclaredMethods();
LinkedHashMap> loadedValues = new LinkedHashMap>();
for (Method method : declaredMethod) {
AnnotationDescription.AnnotationValue, ?> annotationValue = values.get(method.getName());
loadedValues.put(method, annotationValue == null
? DefaultValue.of(method)
: annotationValue.load(classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader));
}
return new AnnotationInvocationHandler(annotationType, loadedValues);
}
/**
* Resolves any primitive type to its wrapper type.
*
* @param type The type to resolve.
* @return The resolved type.
*/
private static Class> asWrapper(Class> type) {
if (type.isPrimitive()) {
if (type == boolean.class) {
return Boolean.class;
} else if (type == byte.class) {
return Byte.class;
} else if (type == short.class) {
return Short.class;
} else if (type == char.class) {
return Character.class;
} else if (type == int.class) {
return Integer.class;
} else if (type == long.class) {
return Long.class;
} else if (type == float.class) {
return Float.class;
} else if (type == double.class) {
return Double.class;
}
}
return type;
}
@Override
public Object invoke(Object proxy, Method method, Object[] arguments) {
if (method.getDeclaringClass() != annotationType) {
if (method.getName().equals(HASH_CODE)) {
return hashCodeRepresentation();
} else if (method.getName().equals(EQUALS) && method.getParameterTypes().length == 1) {
return equalsRepresentation(proxy, arguments[0]);
} else if (method.getName().equals(TO_STRING)) {
return toStringRepresentation();
} else /* method.getName().equals("annotationType") */ {
return annotationType;
}
}
Object value = values.get(method).resolve();
if (!asWrapper(method.getReturnType()).isAssignableFrom(value.getClass())) {
throw new AnnotationTypeMismatchException(method, value.getClass().toString());
}
return value;
}
/**
* Returns the string representation of the represented annotation.
*
* @return The string representation of the represented annotation.
*/
protected String toStringRepresentation() {
StringBuilder toString = new StringBuilder();
toString.append('@');
toString.append(annotationType.getName());
toString.append('(');
boolean firstMember = true;
for (Map.Entry> entry : values.entrySet()) {
if (!entry.getValue().getState().isDefined()) {
continue;
}
if (firstMember) {
firstMember = false;
} else {
toString.append(", ");
}
toString.append(entry.getKey().getName());
toString.append('=');
toString.append(entry.getValue().toString());
}
toString.append(')');
return toString.toString();
}
/**
* Returns the hash code of the represented annotation.
*
* @return The hash code of the represented annotation.
*/
private int hashCodeRepresentation() {
int hashCode = 0;
for (Map.Entry> entry : values.entrySet()) {
if (!entry.getValue().getState().isDefined()) {
continue;
}
hashCode += (127 * entry.getKey().getName().hashCode()) ^ entry.getValue().hashCode();
}
return hashCode;
}
/**
* Checks if another instance is equal to this instance.
*
* @param self The annotation proxy instance.
* @param other The instance to be examined for equality to the represented instance.
* @return {@code true} if the given instance is equal to the represented instance.
*/
private boolean equalsRepresentation(Object self, Object other) {
if (self == other) {
return true;
} else if (!annotationType.isInstance(other)) {
return false;
} else if (Proxy.isProxyClass(other.getClass())) {
InvocationHandler invocationHandler = Proxy.getInvocationHandler(other);
if (invocationHandler instanceof AnnotationInvocationHandler) {
return invocationHandler.equals(this);
}
}
try {
for (Map.Entry> entry : values.entrySet()) {
if (entry.getValue().getState().isResolved()) {
try {
if (!PropertyDispatcher.of(entry.getKey().getReturnType())
.equals(entry.getValue().resolve(), entry.getKey().invoke(other))) {
return false;
}
} catch (RuntimeException exception) {
return false; // Incomplete annotations are not equal to one another.
}
} else {
return false;
}
}
} catch (InvocationTargetException ignored) {
return false;
} catch (IllegalAccessException exception) {
throw new AssertionError(exception);
}
return true;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof AnnotationInvocationHandler)) return false;
AnnotationInvocationHandler that = (AnnotationInvocationHandler) other;
if (!annotationType.equals(that.annotationType)) {
return false;
}
for (Map.Entry entry : values.entrySet()) {
Object value = that.values.get(entry.getKey());
if (!PropertyDispatcher.of(value.getClass()).equals(value, entry.getValue())) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int result = annotationType.hashCode();
result = 31 * result + values.hashCode();
for (Map.Entry entry : values.entrySet()) {
result = 31 * result + PropertyDispatcher.of(entry.getValue().getClass()).hashCode(entry.getValue());
}
return result;
}
@Override
public String toString() {
return "TypePool.LazyTypeDescription.AnnotationInvocationHandler{" +
"annotationType=" + annotationType +
", values=" + values +
'}';
}
/**
* Represents a default value for an annotation property that is not explicitly defined by
* an annotation.
*/
protected static class DefaultValue implements AnnotationDescription.AnnotationValue.Loaded