com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationDescription Maven / Gradle / Ivy
The newest version!
package com.ui4j.bytebuddy.instrumentation.attribute.annotation;
import com.ui4j.bytebuddy.instrumentation.method.MethodDescription;
import com.ui4j.bytebuddy.instrumentation.type.TypeDescription;
import com.ui4j.bytebuddy.instrumentation.type.TypeList;
import com.ui4j.bytebuddy.utility.PropertyDispatcher;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* 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 com.ui4j.bytebuddy.instrumentation.type.TypeDescription}s.
* - {@link java.lang.Enum} instances are represented as
* {@link com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationDescription.EnumerationValue}s.
* - {@link java.lang.annotation.Annotation}s are described as
* {@link com.ui4j.bytebuddy.instrumentation.attribute.annotation.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 {
/**
* Returns the value of the given method for this annotation value. Note that all return values are wrapped as
* described by {@link com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationDescription}.
*
* @param methodDescription The method for the value to be requested.
* @return The value for the given method.
*/
Object getValue(MethodDescription 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 com.ui4j.bytebuddy.instrumentation.attribute.annotation.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 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);
/**
* Represents a value of an {@link java.lang.Enum} which is a value of an
* {@link com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationDescription}.
*/
static interface EnumerationValue {
/**
* Returns the name of this instance's enumeration value.
*
* @return The name of this enumeration constant.
*/
String getValue();
/**
* Returns the type of this enumeration.
*
* @return The type of this enumeration.
*/
TypeDescription getEnumerationType();
/**
* Prepares this enumeration value to be loaded.
*
* @param type A type constant representing the enumeration value.
* @param The enumeration type.
* @return The loaded enumeration constant corresponding to this value.
*/
> T load(Class type);
/**
* An adapter implementation of an enumeration value.
*/
abstract static class AbstractEnumerationValue implements EnumerationValue {
@Override
public boolean equals(Object other) {
return other == this || other instanceof EnumerationValue
&& (((EnumerationValue) other)).getEnumerationType().equals(getEnumerationType())
&& (((EnumerationValue) other)).getValue().equals(getValue());
}
@Override
public int hashCode() {
return getValue().hashCode() + 31 * getEnumerationType().hashCode();
}
@Override
public String toString() {
return getValue();
}
}
/**
* An enumeration value representing a loaded enumeration.
*/
static class ForLoadedEnumeration extends AbstractEnumerationValue {
/**
* The enumeration value.
*/
private final Enum> value;
/**
* Creates a new enumeration value representation for a loaded enumeration.
*
* @param value The value to represent.
*/
public ForLoadedEnumeration(Enum> value) {
this.value = value;
}
/**
* Enlists a given array of loaded enumerations as enumeration values.
*
* @param enumerations The enumerations to represent.
* @return A list of the given enumerations.
*/
public static List asList(Enum>[] enumerations) {
List result = new ArrayList(enumerations.length);
for (Enum> enumeration : enumerations) {
result.add(new ForLoadedEnumeration(enumeration));
}
return result;
}
@Override
public String getValue() {
return value.name();
}
@Override
public TypeDescription getEnumerationType() {
return new TypeDescription.ForLoadedType(value.getDeclaringClass());
}
@Override
@SuppressWarnings("unchecked")
public > T load(Class type) {
if (value.getDeclaringClass() != type) {
throw new IllegalArgumentException(type + " does not represent " + value);
}
return (T) value;
}
}
}
/**
* 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.
*/
static 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 adapter implementaton of an annotation.
*/
abstract static class AbstractAnnotationDescription implements AnnotationDescription {
@Override
public T getValue(MethodDescription methodDescription, Class type) {
return type.cast(getValue(methodDescription));
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof AnnotationDescription)) {
return false;
}
AnnotationDescription annotationDescription = ((AnnotationDescription) other);
if (!annotationDescription.getAnnotationType().equals(getAnnotationType())) {
return false;
}
for (MethodDescription methodDescription : getAnnotationType().getDeclaredMethods()) {
Object value = getValue(methodDescription);
if (!PropertyDispatcher.of(value.getClass()).equals(value, annotationDescription.getValue(methodDescription))) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int hashCode = 0;
for (MethodDescription methodDescription : getAnnotationType().getDeclaredMethods()) {
Object value = getValue(methodDescription);
hashCode += 31 * PropertyDispatcher.of(value.getClass()).hashCode(value);
}
return hashCode;
}
@Override
public String toString() {
StringBuilder toString = new StringBuilder();
toString.append('@');
toString.append(getAnnotationType().getName());
toString.append('(');
boolean firstMember = true;
for (MethodDescription methodDescription : getAnnotationType().getDeclaredMethods()) {
if (firstMember) {
firstMember = false;
} else {
toString.append(", ");
}
toString.append(methodDescription.getName());
toString.append('=');
Object value = getValue(methodDescription);
toString.append(PropertyDispatcher.of(value.getClass()).toString(value));
}
toString.append(')');
return toString.toString();
}
/**
* An abstract implementation of a loadable annotation description.
*
* @param The annotation type this instance was prepared for.
*/
public abstract static class ForPrepared extends AbstractAnnotationDescription implements Loadable {
/**
* The error message to be displayed on a {@link java.lang.ClassNotFoundException}.
*/
public static final String ERROR_MESSAGE = "Could not load a type that is linked by the annotation value";
@Override
public S loadSilent() {
try {
return load();
} catch (ClassNotFoundException e) {
throw new IllegalStateException(ERROR_MESSAGE, e);
}
}
@Override
public S loadSilent(ClassLoader classLoader) {
try {
return load(classLoader);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(ERROR_MESSAGE, e);
}
}
}
}
/**
* A description of an already loaded annotation.
*
* @param The type of the annotation.
*/
static class ForLoadedAnnotation extends AbstractAnnotationDescription.ForPrepared implements Loadable {
/**
* The represented annotation value.
*/
private final S annotation;
/**
* Creates a new annotation description for a loaded annotation.
*
* @param annotation The annotation to represent.
*/
protected ForLoadedAnnotation(S annotation) {
this.annotation = annotation;
}
/**
* Creates a description of the given annotation.
*
* @param annotation The annotation to be described.
* @param The type of the annotation.
* @return A description of the given annotation.
*/
public static Loadable of(U annotation) {
return new ForLoadedAnnotation(annotation);
}
/**
* A helper method for converting a loaded type into a representation that is also capable of representing
* unloaded descriptions of annotation values as specified by
* {@link com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationDescription}.
*
* @param value The loaded value.
* @param typeDescription The annotation type of the value. This cannot be inferred as enumerations
* can implement annotation interfaces and because annotations could be implemented as
* an enumeration what creates an ambiguity.
* @return The wrapped representation as specified by
* {@link com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationDescription}.
*/
public static Object wrap(Object value, TypeDescription typeDescription) {
// Because enums can implement annotation interfaces, the enum property needs to be checked first.
if (typeDescription.represents(Class.class)) {
value = new TypeDescription.ForLoadedType((Class>) value);
} else if (typeDescription.represents(Class[].class)) {
value = new TypeList.ForLoadedType((Class>[]) value)
.toArray(new TypeDescription[((Class>[]) value).length]);
} else if (typeDescription.isAssignableTo(Enum.class)) {
value = new EnumerationValue.ForLoadedEnumeration((Enum>) value);
} else if (typeDescription.isAssignableTo(Enum[].class)) {
value = EnumerationValue.ForLoadedEnumeration.asList((Enum>[]) value)
.toArray(new EnumerationValue[((Enum>[]) value).length]);
} else if (typeDescription.isAssignableTo(Annotation.class)) {
value = ForLoadedAnnotation.of((Annotation) value);
} else if (typeDescription.isAssignableTo(Annotation[].class)) {
value = new AnnotationList.ForLoadedAnnotation((Annotation[]) value)
.toArray(new AnnotationDescription[((Annotation[]) value).length]);
}
return value;
}
@Override
public S load() {
return annotation;
}
@Override
public S load(ClassLoader classLoader) {
return annotation;
}
@Override
public Object getValue(MethodDescription methodDescription) {
if (!methodDescription.getDeclaringType().represents(annotation.annotationType())) {
throw new IllegalArgumentException(methodDescription + " does not represent " + annotation.annotationType());
}
try {
boolean visible = methodDescription.isVisibleTo(new TypeDescription.ForLoadedType(getClass()));
Method method = methodDescription instanceof MethodDescription.ForLoadedMethod
? ((MethodDescription.ForLoadedMethod) methodDescription).getLoadedMethod()
: null;
if (method == null || (!visible && !method.isAccessible())) {
method = annotation.annotationType().getDeclaredMethod(methodDescription.getName());
if (!visible) {
method.setAccessible(true);
}
}
return wrap(method.invoke(annotation), methodDescription.getReturnType());
} catch (Exception e) {
throw new IllegalStateException("Cannot access annotation property " + methodDescription, e);
}
}
@Override
@SuppressWarnings("unchecked")
public Loadable prepare(Class annotationType) {
if (!annotation.annotationType().equals(annotationType)) {
throw new IllegalArgumentException("Annotation is not of type " + annotationType);
}
return (Loadable) this;
}
@Override
public TypeDescription getAnnotationType() {
return new TypeDescription.ForLoadedType(annotation.annotationType());
}
}
}