net.bytebuddy.description.annotation.AnnotationDescription Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2014 - Present Rafael Winterhalter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bytebuddy.description.annotation;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.build.AccessControllerPlugin;
import net.bytebuddy.build.CachedReturnPlugin;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.description.enumeration.EnumerationDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.utility.nullability.AlwaysNull;
import net.bytebuddy.utility.nullability.MaybeNull;
import net.bytebuddy.utility.privilege.SetAccessibleAction;
import java.lang.annotation.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.PrivilegedAction;
import java.util.*;
import static net.bytebuddy.matcher.ElementMatchers.*;
/**
* 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 net.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 a nonexistent annotation in a type-safe manner.
*/
@AlwaysNull
AnnotationDescription.Loadable> UNDEFINED = null;
/**
* Returns a value of this annotation.
*
* @param property The name of the property being accessed.
* @return The value for the supplied property.
*/
AnnotationValue, ?> getValue(String property);
/**
* Returns a value of this annotation.
*
* @param property The property being accessed.
* @return The value for the supplied property.
*/
AnnotationValue, ?> getValue(MethodDescription.InDefinedShape property);
/**
* 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 supported on the supplied element type.
*
* @param elementType The element type to check.
* @return {@code true} if the supplied element type is supported by this annotation.
*/
boolean isSupportedOn(ElementType elementType);
/**
* Checks if this annotation is supported on the supplied element type.
*
* @param elementType The element type to check.
* @return {@code true} if the supplied element type is supported by this annotation.
*/
boolean isSupportedOn(String elementType);
/**
* 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();
/**
* 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.
*/
S load();
}
/**
* A rendering dispatcher is responsible for resolving annotation descriptions to {@link String} representations.
*/
enum RenderingDispatcher {
/**
* A rendering dispatcher for any VM previous to Java 14.
*/
LEGACY_VM,
/**
* A rendering dispatcher for Java 14 until Java 18.
*/
JAVA_14_CAPABLE_VM {
@Override
public void appendPrefix(StringBuilder toString, String key, int count) {
if (count > 1 || !key.equals("value")) {
super.appendPrefix(toString, key, count);
}
}
},
/**
* A rendering dispatcher for Java 19 onward.
*/
JAVA_19_CAPABLE_VM {
@Override
public void appendPrefix(StringBuilder toString, String key, int count) {
if (count > 1 || !key.equals("value")) {
super.appendPrefix(toString, key, count);
}
}
@Override
public void appendType(StringBuilder toString, TypeDescription typeDescription) {
toString.append(typeDescription.getCanonicalName());
}
};
/**
* The rendering dispatcher for the current VM.
*/
public static final RenderingDispatcher CURRENT;
/*
* Initializes the rendering dispatcher.
*/
static {
ClassFileVersion classFileVersion = ClassFileVersion.ofThisVm(ClassFileVersion.JAVA_V5);
if (classFileVersion.isAtLeast(ClassFileVersion.JAVA_V19)) {
CURRENT = RenderingDispatcher.JAVA_19_CAPABLE_VM;
} else if (classFileVersion.isAtLeast(ClassFileVersion.JAVA_V14)) {
CURRENT = RenderingDispatcher.JAVA_14_CAPABLE_VM;
} else {
CURRENT = RenderingDispatcher.LEGACY_VM;
}
}
/**
* Appends the key property prefix to a string builder representing an annotation's string representation.
*
* @param toString The string builder that creates the string representation.
* @param key The key's name.
* @param count The property count.
*/
public void appendPrefix(StringBuilder toString, String key, int count) {
toString.append(key).append('=');
}
/**
* Appends the type name of the annotation being rendered.
*
* @param toString The string builder that creates the string representation.
* @param typeDescription The annotation type being rendered.
*/
public void appendType(StringBuilder toString, TypeDescription typeDescription) {
toString.append(typeDescription.getName());
}
}
/**
* 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 name of the {@link Annotation#annotationType()} method.
*/
private static final String ANNOTATION_TYPE = "annotationType";
/**
* An empty array that can be used to indicate no arguments to avoid an allocation on a reflective call.
*/
private static final Object[] NO_ARGUMENT = new Object[0];
/**
* 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;
}
/**
* Creates a proxy instance for the supplied annotation type and 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 A proxy for the annotation type and values.
*/
@SuppressWarnings("unchecked")
public static S of(@MaybeNull ClassLoader classLoader,
Class annotationType,
Map> values) {
LinkedHashMap> loadedValues = new LinkedHashMap>();
for (Method method : annotationType.getDeclaredMethods()) {
AnnotationValue, ?> annotationValue = values.get(method.getName());
if (annotationValue == null) {
Object defaultValue = method.getDefaultValue();
loadedValues.put(method, (defaultValue == null
? new AnnotationValue.ForMissingValue(new TypeDescription.ForLoadedType(method.getDeclaringClass()), method.getName())
: AnnotationDescription.ForLoadedAnnotation.asValue(defaultValue, method.getReturnType())).load(classLoader));
} else {
loadedValues.put(method, annotationValue.filter(new MethodDescription.ForLoadedMethod(method)).load(classLoader));
}
}
return (S) Proxy.newProxyInstance(classLoader, new Class>[]{annotationType}, new AnnotationInvocationHandler(annotationType, loadedValues));
}
/**
* {@inheritDoc}
*/
public Object invoke(Object proxy, Method method, @MaybeNull Object[] argument) {
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, argument[0]);
} else if (method.getName().equals(TO_STRING)) {
return toStringRepresentation();
} else if (method.getName().equals(ANNOTATION_TYPE)) {
return annotationType;
} else {
throw new IllegalStateException("Unexpected method: " + method);
}
}
return values.get(method).resolve();
}
/**
* 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('@');
RenderingDispatcher.CURRENT.appendType(toString, TypeDescription.ForLoadedType.of(annotationType));
toString.append('(');
boolean firstMember = true;
for (Map.Entry> entry : values.entrySet()) {
if (!entry.getValue().getState().isDefined()) {
continue;
}
if (firstMember) {
firstMember = false;
} else {
toString.append(", ");
}
RenderingDispatcher.CURRENT.appendPrefix(toString, entry.getKey().getName(), values.entrySet().size());
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()) {
try {
if (!entry.getValue().represents(entry.getKey().invoke(other, NO_ARGUMENT))) {
return false;
}
} catch (RuntimeException exception) {
return false; // Incomplete annotations are not equal to one another.
}
}
return true;
} catch (InvocationTargetException ignored) {
return false;
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Could not access annotation property", exception);
}
}
@Override
@CachedReturnPlugin.Enhance("hashCode")
public int hashCode() {
int result = annotationType.hashCode();
result = 31 * result + values.hashCode();
for (Map.Entry entry : values.entrySet()) {
result = 31 * result + entry.getValue().hashCode();
}
return result;
}
@Override
public boolean equals(@MaybeNull Object other) {
if (this == other) {
return true;
} else if (!(other instanceof AnnotationInvocationHandler)) {
return false;
}
AnnotationInvocationHandler> that = (AnnotationInvocationHandler>) other;
if (!annotationType.equals(that.annotationType)) {
return false;
}
for (Map.Entry> entry : values.entrySet()) {
if (!entry.getValue().equals(that.values.get(entry.getKey()))) {
return false;
}
}
return true;
}
}
/**
* An adapter implementation of an annotation.
*/
abstract class AbstractBase implements AnnotationDescription {
/**
* An array containing all element types that are a legal annotation target when such a target
* is not specified explicitly.
*/
private static final Set DEFAULT_TARGET;
/**
* A description of the {@link Retention#value()} method.
*/
private static final MethodDescription.InDefinedShape RETENTION_VALUE;
/**
* A description of the {@link Target#value()} method.
*/
private static final MethodDescription.InDefinedShape TARGET_VALUE;
/*
* Resolves common annotation properties.
*/
static {
DEFAULT_TARGET = new HashSet();
for (ElementType elementType : ElementType.values()) {
if (!elementType.name().equals("TYPE_PARAMETER")) {
DEFAULT_TARGET.add(elementType);
}
}
RETENTION_VALUE = TypeDescription.ForLoadedType.of(Retention.class)
.getDeclaredMethods()
.filter(named("value"))
.getOnly();
TARGET_VALUE = TypeDescription.ForLoadedType.of(Target.class)
.getDeclaredMethods()
.filter(named("value"))
.getOnly();
}
/**
* {@inheritDoc}
*/
public AnnotationValue, ?> getValue(String property) {
MethodList candidates = getAnnotationType().getDeclaredMethods().filter(named(property)
.and(takesArguments(0))
.and(isPublic())
.and(not(isStatic())));
if (candidates.size() == 1) {
return getValue(candidates.getOnly());
} else {
throw new IllegalArgumentException("Unknown property of " + getAnnotationType() + ": " + property);
}
}
/**
* {@inheritDoc}
*/
public RetentionPolicy getRetention() {
AnnotationDescription.Loadable retention = getAnnotationType().getDeclaredAnnotations().ofType(Retention.class);
return retention == null
? RetentionPolicy.CLASS
: retention.getValue(RETENTION_VALUE).load(ClassLoadingStrategy.BOOTSTRAP_LOADER).resolve(RetentionPolicy.class);
}
/**
* {@inheritDoc}
*/
public Set getElementTypes() {
AnnotationDescription.Loadable target = getAnnotationType().getDeclaredAnnotations().ofType(Target.class);
return target == null
? Collections.unmodifiableSet(DEFAULT_TARGET)
: new HashSet(Arrays.asList(target.getValue(TARGET_VALUE).load(ClassLoadingStrategy.BOOTSTRAP_LOADER).resolve(ElementType[].class)));
}
/**
* {@inheritDoc}
*/
public boolean isSupportedOn(ElementType elementType) {
return isSupportedOn(elementType.name());
}
/**
* {@inheritDoc}
*/
public boolean isSupportedOn(String elementType) {
AnnotationDescription.Loadable target = getAnnotationType().getDeclaredAnnotations().ofType(Target.class);
if (target == null) {
if (elementType.equals("TYPE_USE")) {
return true;
}
for (ElementType candidate : DEFAULT_TARGET) {
if (candidate.name().equals(elementType)) {
return true;
}
}
} else {
for (EnumerationDescription enumerationDescription : target.getValue(TARGET_VALUE).resolve(EnumerationDescription[].class)) {
if (enumerationDescription.getValue().equals(elementType)) {
return true;
}
}
}
return false;
}
/**
* {@inheritDoc}
*/
public boolean isInherited() {
return getAnnotationType().getDeclaredAnnotations().isAnnotationPresent(Inherited.class);
}
/**
* {@inheritDoc}
*/
public boolean isDocumented() {
return getAnnotationType().getDeclaredAnnotations().isAnnotationPresent(Documented.class);
}
@Override
@CachedReturnPlugin.Enhance("hashCode")
public int hashCode() {
int hashCode = 0;
for (MethodDescription.InDefinedShape methodDescription : getAnnotationType().getDeclaredMethods()) {
hashCode += 31 * getValue(methodDescription).hashCode();
}
return hashCode;
}
@Override
public boolean equals(@MaybeNull Object other) {
if (this == other) {
return true;
} else if (!(other instanceof AnnotationDescription)) {
return false;
}
AnnotationDescription annotationDescription = ((AnnotationDescription) other);
TypeDescription annotationType = getAnnotationType();
if (!annotationDescription.getAnnotationType().equals(annotationType)) {
return false;
}
for (MethodDescription.InDefinedShape methodDescription : annotationType.getDeclaredMethods()) {
if (!getValue(methodDescription).equals(annotationDescription.getValue(methodDescription))) {
return false;
}
}
return true;
}
@Override
public String toString() {
TypeDescription annotationType = getAnnotationType();
StringBuilder toString = new StringBuilder().append('@');
RenderingDispatcher.CURRENT.appendType(toString, annotationType);
toString.append('(');
boolean firstMember = true;
for (MethodDescription.InDefinedShape methodDescription : annotationType.getDeclaredMethods()) {
AnnotationValue, ?> value = getValue(methodDescription);
if (value.getState() == AnnotationValue.State.UNDEFINED) {
continue;
} else if (firstMember) {
firstMember = false;
} else {
toString.append(", ");
}
RenderingDispatcher.CURRENT.appendPrefix(toString, methodDescription.getName(), annotationType.getDeclaredMethods().size());
toString.append(value);
}
return toString.append(')').toString();
}
}
/**
* A description of an already loaded annotation.
*
* @param The type of the annotation.
*/
class ForLoadedAnnotation extends AbstractBase implements Loadable {
/**
* An empty array that can be used to indicate no arguments to avoid an allocation on a reflective call.
*/
private static final Object[] NO_ARGUMENT = new Object[0];
/**
* The represented annotation value.
*/
private final S annotation;
/**
* The annotation's loaded type which might be loaded by a different class loader than the value's
* annotation type but must be structurally equal to it.
*/
private final Class annotationType;
/**
* Creates a new annotation description for a loaded annotation.
*
* @param annotation The annotation to represent.
*/
@SuppressWarnings("unchecked")
protected ForLoadedAnnotation(S annotation) {
this(annotation, (Class) annotation.annotationType());
}
/**
* Creates a new annotation description for a loaded annotation.
*
* @param annotation The annotation to represent.
* @param annotationType The annotation's loaded type which might be loaded by a different class loader than the value's
* annotation type but must be structurally equal to it.
*/
private ForLoadedAnnotation(S annotation, Class annotationType) {
this.annotation = annotation;
this.annotationType = annotationType;
}
/**
* A proxy for {@code java.security.AccessController#doPrivileged} that is activated if available.
*
* @param action The action to execute from a privileged context.
* @param The type of the action's resolved value.
* @return The action's resolved value.
*/
@AccessControllerPlugin.Enhance
private static T doPrivileged(PrivilegedAction action) {
return action.run();
}
/**
* 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);
}
/**
* {@inheritDoc}
*/
public S load() {
return annotationType == annotation.annotationType()
? annotation
: AnnotationInvocationHandler.of(annotationType.getClassLoader(), annotationType, asValue(annotation));
}
/**
* Extracts the annotation values of an annotation into a property map.
*
* @param annotation The annotation to convert.
* @return A mapping of property names to their annotation value.
*/
@SuppressWarnings("rawtypes")
private static Map> asValue(Annotation annotation) {
Map> annotationValues = new HashMap>();
for (Method property : annotation.annotationType().getDeclaredMethods()) {
try {
annotationValues.put(property.getName(), asValue(property.invoke(annotation, NO_ARGUMENT), property.getReturnType()));
} catch (InvocationTargetException exception) {
Throwable cause = exception.getTargetException();
if (cause instanceof TypeNotPresentException) {
annotationValues.put(property.getName(), new AnnotationValue.ForMissingType(((TypeNotPresentException) cause).typeName()));
} else if (cause instanceof EnumConstantNotPresentException) {
annotationValues.put(property.getName(), new AnnotationValue.ForEnumerationDescription.WithUnknownConstant(
new TypeDescription.ForLoadedType(((EnumConstantNotPresentException) cause).enumType()),
((EnumConstantNotPresentException) cause).constantName()));
} else if (cause instanceof AnnotationTypeMismatchException) {
annotationValues.put(property.getName(), new AnnotationValue.ForMismatchedType(
new MethodDescription.ForLoadedMethod(((AnnotationTypeMismatchException) cause).element()),
((AnnotationTypeMismatchException) cause).foundType()));
} else if (!(cause instanceof IncompleteAnnotationException)) {
throw new IllegalStateException("Cannot read " + property, cause);
}
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Cannot access " + property, exception);
}
}
return annotationValues;
}
/**
* Transforms an annotation property to an annotation value.
*
* @param type The annotation's type.
* @param value The annotations value.
* @return An annotation value representation.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static AnnotationValue, ?> asValue(Object value, Class> type) {
// Because enums can implement annotation interfaces, the enum property needs to be checked first.
if (Enum.class.isAssignableFrom(type)) {
return AnnotationValue.ForEnumerationDescription.of(new EnumerationDescription.ForLoadedEnumeration((Enum) value));
} else if (Enum[].class.isAssignableFrom(type)) {
Enum>[] element = (Enum>[]) value;
EnumerationDescription[] enumerationDescription = new EnumerationDescription[element.length];
int index = 0;
for (Enum> anElement : element) {
enumerationDescription[index++] = new EnumerationDescription.ForLoadedEnumeration(anElement);
}
return AnnotationValue.ForDescriptionArray.of(TypeDescription.ForLoadedType.of(type.getComponentType()), enumerationDescription);
} else if (Annotation.class.isAssignableFrom(type)) {
return AnnotationValue.ForAnnotationDescription.of(TypeDescription.ForLoadedType.of(type), asValue((Annotation) value));
} else if (Annotation[].class.isAssignableFrom(type)) {
Annotation[] element = (Annotation[]) value;
AnnotationDescription[] annotationDescription = new AnnotationDescription[element.length];
int index = 0;
for (Annotation anElement : element) {
annotationDescription[index++] = new AnnotationDescription.Latent(TypeDescription.ForLoadedType.of(type.getComponentType()), asValue(anElement));
}
return AnnotationValue.ForDescriptionArray.of(TypeDescription.ForLoadedType.of(type.getComponentType()), annotationDescription);
} else if (Class.class.isAssignableFrom(type)) {
return AnnotationValue.ForTypeDescription.of(TypeDescription.ForLoadedType.of((Class>) value));
} else if (Class[].class.isAssignableFrom(type)) {
Class>[] element = (Class>[]) value;
TypeDescription[] typeDescription = new TypeDescription[element.length];
int index = 0;
for (Class> anElement : element) {
typeDescription[index++] = TypeDescription.ForLoadedType.of(anElement);
}
return AnnotationValue.ForDescriptionArray.of(typeDescription);
} else {
return AnnotationValue.ForConstant.of(value);
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings({"deprecation", "rawtypes"})
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should always be wrapped for clarity.")
public AnnotationValue, ?> getValue(MethodDescription.InDefinedShape property) {
if (!property.getDeclaringType().represents(annotation.annotationType())) {
throw new IllegalArgumentException(property + " does not represent " + annotation.annotationType());
}
try {
boolean accessible = property.getDeclaringType().isPublic(); // method is required to be public
Method method = property instanceof MethodDescription.ForLoadedMethod
? ((MethodDescription.ForLoadedMethod) property).getLoadedMethod()
: null;
if (method == null || method.getDeclaringClass() != annotation.annotationType() || (!accessible && !method.isAccessible())) {
method = annotation.annotationType().getMethod(property.getName());
if (!accessible) {
doPrivileged(new SetAccessibleAction(method));
}
}
return asValue(method.invoke(annotation, NO_ARGUMENT), method.getReturnType()).filter(property);
} catch (InvocationTargetException exception) {
Throwable cause = exception.getTargetException();
if (cause instanceof TypeNotPresentException) {
return new AnnotationValue.ForMissingType(((TypeNotPresentException) cause).typeName());
} else if (cause instanceof EnumConstantNotPresentException) {
return new AnnotationValue.ForEnumerationDescription.WithUnknownConstant(
new TypeDescription.ForLoadedType(((EnumConstantNotPresentException) cause).enumType()),
((EnumConstantNotPresentException) cause).constantName());
} else if (cause instanceof AnnotationTypeMismatchException) {
return new AnnotationValue.ForMismatchedType(
new MethodDescription.ForLoadedMethod(((AnnotationTypeMismatchException) cause).element()),
((AnnotationTypeMismatchException) cause).foundType());
} else if (cause instanceof IncompleteAnnotationException) {
return new AnnotationValue.ForMissingValue(
new TypeDescription.ForLoadedType(((IncompleteAnnotationException) cause).annotationType()),
((IncompleteAnnotationException) cause).elementName());
} else {
throw new IllegalStateException("Error reading annotation property " + property, cause);
}
} catch (Exception exception) {
throw new IllegalStateException("Cannot access annotation property " + property, exception);
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Loadable prepare(Class annotationType) {
if (!annotation.annotationType().getName().equals(annotationType.getName())) {
throw new IllegalArgumentException(annotationType + " does not represent " + annotation.annotationType());
}
return annotationType == annotation.annotationType()
? (Loadable) this
: new ForLoadedAnnotation((T) annotation, annotationType);
}
/**
* {@inheritDoc}
*/
public TypeDescription getAnnotationType() {
return TypeDescription.ForLoadedType.of(annotation.annotationType());
}
}
/**
* A latent description of an annotation value that is defined explicitly.
*/
class Latent extends AbstractBase {
/**
* The type of the annotation.
*/
private final TypeDescription annotationType;
/**
* The values of the annotation mapped by their property name.
*/
private final Map> annotationValues;
/**
* Creates a new latent annotation description.
*
* @param annotationType The type of the annotation.
* @param annotationValues The values of the annotation mapped by their property name.
*/
protected Latent(TypeDescription annotationType, Map> annotationValues) {
this.annotationType = annotationType;
this.annotationValues = annotationValues;
}
/**
* {@inheritDoc}
*/
public AnnotationValue, ?> getValue(MethodDescription.InDefinedShape property) {
if (!property.getDeclaringType().equals(annotationType)) {
throw new IllegalArgumentException("Not a property of " + annotationType + ": " + property);
}
AnnotationValue, ?> value = annotationValues.get(property.getName());
if (value != null) {
return value.filter(property);
}
AnnotationValue, ?> defaultValue = property.getDefaultValue();
return defaultValue == null
? new AnnotationValue.ForMissingValue(annotationType, property.getName())
: defaultValue;
}
/**
* {@inheritDoc}
*/
public TypeDescription getAnnotationType() {
return annotationType;
}
/**
* {@inheritDoc}
*/
public Loadable prepare(Class annotationType) {
if (!this.annotationType.represents(annotationType)) {
throw new IllegalArgumentException(annotationType + " does not represent " + this.annotationType);
}
return new Loadable(annotationType);
}
/**
* A loadable annotation description of a latent annotation description.
*
* @param The annotation type.
*/
protected class Loadable extends AbstractBase implements AnnotationDescription.Loadable {
/**
* The annotation type.
*/
private final Class annotationType;
/**
* Creates a loadable version of a latent annotation description.
*
* @param annotationType The annotation type.
*/
protected Loadable(Class annotationType) {
this.annotationType = annotationType;
}
/**
* {@inheritDoc}
*/
public S load() {
return AnnotationDescription.AnnotationInvocationHandler.of(annotationType.getClassLoader(), annotationType, annotationValues);
}
/**
* {@inheritDoc}
*/
public AnnotationValue, ?> getValue(MethodDescription.InDefinedShape property) {
return Latent.this.getValue(property);
}
/**
* {@inheritDoc}
*/
public TypeDescription getAnnotationType() {
return TypeDescription.ForLoadedType.of(annotationType);
}
/**
* {@inheritDoc}
*/
public Loadable prepare(Class annotationType) {
return Latent.this.prepare(annotationType);
}
}
}
/**
* A builder for pragmatically creating {@link net.bytebuddy.description.annotation.AnnotationDescription}.
*/
@HashCodeAndEqualsPlugin.Enhance
class Builder {
/**
* The annotation type.
*/
private final TypeDescription annotationType;
/**
* A mapping of annotation properties to their annotation values.
*/
private final Map> annotationValues;
/**
* Creates a builder for an annotation description.
*
* @param annotationType The annotation type.
* @param annotationValues A mapping of annotation properties to their annotation values.
*/
protected Builder(TypeDescription annotationType, Map> annotationValues) {
this.annotationType = annotationType;
this.annotationValues = annotationValues;
}
/**
* Creates a builder for creating an annotation of the given type.
*
* @param annotationType The annotation type.
* @return A builder for creating an annotation of the given type.
*/
public static Builder ofType(Class extends Annotation> annotationType) {
return ofType(TypeDescription.ForLoadedType.of(annotationType));
}
/**
* Creates a builder for creating an annotation of the given type.
*
* @param annotationType A description of the annotation type.
* @return A builder for creating an annotation of the given type.
*/
public static Builder ofType(TypeDescription annotationType) {
if (!annotationType.isAnnotation()) {
throw new IllegalArgumentException("Not an annotation type: " + annotationType);
}
return new Builder(annotationType, Collections.>emptyMap());
}
/**
* Returns a builder with the additional, given property.
*
* @param property The name of the property to define.
* @param value An explicit description of the annotation value.
* @return A builder with the additional, given property.
*/
public Builder define(String property, AnnotationValue, ?> value) {
MethodList methodDescriptions = annotationType.getDeclaredMethods().filter(named(property));
if (methodDescriptions.isEmpty()) {
throw new IllegalArgumentException(annotationType + " does not define a property named " + property);
}
Map> annotationValues = new HashMap>(this.annotationValues);
if (annotationValues.put(methodDescriptions.getOnly().getName(), value) != null) {
throw new IllegalArgumentException("Property already defined: " + property);
}
return new Builder(annotationType, annotationValues);
}
/**
* Returns a builder with the additional enumeration property.
*
* @param property The name of the property to define.
* @param value The enumeration value to define.
* @return A builder with the additional enumeration property.
*/
public Builder define(String property, Enum> value) {
return define(property, new EnumerationDescription.ForLoadedEnumeration(value));
}
/**
* Returns a builder with the additional enumeration property.
*
* @param property The name of the property to define.
* @param enumerationType The type of the enumeration.
* @param value The enumeration value to define.
* @return A builder with the additional enumeration property.
*/
public Builder define(String property, TypeDescription enumerationType, String value) {
return define(property, new EnumerationDescription.Latent(enumerationType, value));
}
/**
* Returns a builder with the additional enumeration property.
*
* @param property The name of the property to define.
* @param value A description of the enumeration value to define.
* @return A builder with the additional enumeration property.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public Builder define(String property, EnumerationDescription value) {
return define(property, AnnotationValue.ForEnumerationDescription.of(value));
}
/**
* Returns a builder with the additional annotation property.
*
* @param property The name of the property to define.
* @param annotation The annotation value to define.
* @return A builder with the additional annotation property.
*/
public Builder define(String property, Annotation annotation) {
return define(property, new ForLoadedAnnotation(annotation));
}
/**
* Returns a builder with the additional annotation property.
*
* @param property The name of the property to define.
* @param annotationDescription A description of the annotation value to define.
* @return A builder with the additional annotation property.
*/
public Builder define(String property, AnnotationDescription annotationDescription) {
return define(property, new AnnotationValue.ForAnnotationDescription(annotationDescription));
}
/**
* Returns a builder with the additional class property.
*
* @param property The name of the property to define.
* @param type The class value to define.
* @return A builder with the additional class property.
*/
public Builder define(String property, Class> type) {
return define(property, TypeDescription.ForLoadedType.of(type));
}
/**
* Returns a builder with the additional class property.
*
* @param property The name of the property to define.
* @param typeDescription A description of the type to define as a property value.
* @return A builder with the additional class property.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public Builder define(String property, TypeDescription typeDescription) {
return define(property, AnnotationValue.ForTypeDescription.of(typeDescription));
}
/**
* Returns a builder with the additional enumeration array property.
*
* @param property The name of the property to define.
* @param enumerationType The type of the enumeration, i.e. the component type of the enumeration array.
* @param value The enumeration values to be contained by the array.
* @param The enumeration type.
* @return A builder with the additional class property.
*/
@SuppressWarnings("unchecked")
public > Builder defineEnumerationArray(String property, Class enumerationType, T... value) {
EnumerationDescription[] enumerationDescription = new EnumerationDescription[value.length];
int index = 0;
for (T aValue : value) {
enumerationDescription[index++] = new EnumerationDescription.ForLoadedEnumeration(aValue);
}
return defineEnumerationArray(property, TypeDescription.ForLoadedType.of(enumerationType), enumerationDescription);
}
/**
* Returns a builder with the additional enumeration array property.
*
* @param property The name of the property to define.
* @param enumerationType The type of the enumerations, i.e. is the component type of the enumeration array.
* @param value The enumeration values to be contained by the array.
* @return A builder with the additional enumeration property.
*/
public Builder defineEnumerationArray(String property, TypeDescription enumerationType, String... value) {
if (!enumerationType.isEnum()) {
throw new IllegalArgumentException("Not an enumeration type: " + enumerationType);
}
EnumerationDescription[] enumerationDescription = new EnumerationDescription[value.length];
for (int i = 0; i < value.length; i++) {
enumerationDescription[i] = new EnumerationDescription.Latent(enumerationType, value[i]);
}
return defineEnumerationArray(property, enumerationType, enumerationDescription);
}
/**
* Returns a builder with the additional enumeration array property.
*
* @param property The name of the property to define.
* @param enumerationType The type of the enumerations, i.e. the component type of the enumeration array.
* @param value Descriptions of the enumerations to be contained by the array.
* @return A builder with the additional enumeration property.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public Builder defineEnumerationArray(String property, TypeDescription enumerationType, EnumerationDescription... value) {
return define(property, AnnotationValue.ForDescriptionArray.of(enumerationType, value));
}
/**
* Returns a builder with the additional annotation array property.
*
* @param property The name of the property to define.
* @param annotationType The type of the annotations, i.e. the component type of the enumeration array.
* @param annotation The annotation values to be contained by the array.
* @param The annotation type.
* @return A builder with the additional annotation property.
*/
@SuppressWarnings("unchecked")
public Builder defineAnnotationArray(String property, Class annotationType, T... annotation) {
return defineAnnotationArray(property,
TypeDescription.ForLoadedType.of(annotationType),
new AnnotationList.ForLoadedAnnotations(annotation).toArray(new AnnotationDescription[0]));
}
/**
* Returns a builder with the additional annotation array property.
*
* @param property The name of the property to define.
* @param annotationType The type of the annotations, i.e. the component type of the enumeration array.
* @param annotationDescription Descriptions of the annotation values to be contained by the array.
* @return A builder with the additional annotation property.
*/
public Builder defineAnnotationArray(String property, TypeDescription annotationType, AnnotationDescription... annotationDescription) {
return define(property, AnnotationValue.ForDescriptionArray.of(annotationType, annotationDescription));
}
/**
* Returns a builder with the additional type array property.
*
* @param property The name of the property to define.
* @param type The types that should be contained by the array.
* @return A builder with the additional type array property.
*/
public Builder defineTypeArray(String property, Class>... type) {
return defineTypeArray(property, new TypeList.ForLoadedTypes(type).toArray(new TypeDescription[0]));
}
/**
* Returns a builder with the additional type array property.
*
* @param property The name of the property to define.
* @param typeDescription Descriptions of the types that should be contained by the array.
* @return A builder with the additional type array property.
*/
public Builder defineTypeArray(String property, TypeDescription... typeDescription) {
return define(property, AnnotationValue.ForDescriptionArray.of(typeDescription));
}
/**
* Returns a builder with the additional {@code boolean} property.
*
* @param property The name of the property to define.
* @param value The {@code boolean} value to define for the property.
* @return A builder with the additional {@code boolean} property.
*/
public Builder define(String property, boolean value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code byte} property.
*
* @param property The name of the property to define.
* @param value The {@code byte} value to define for the property.
* @return A builder with the additional {@code byte} property.
*/
public Builder define(String property, byte value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code char} property.
*
* @param property The name of the property to define.
* @param value The {@code char} value to define for the property.
* @return A builder with the additional {@code char} property.
*/
public Builder define(String property, char value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code short} property.
*
* @param property The name of the property to define.
* @param value The {@code short} value to define for the property.
* @return A builder with the additional {@code short} property.
*/
public Builder define(String property, short value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code int} property.
*
* @param property The name of the property to define.
* @param value The {@code int} value to define for the property.
* @return A builder with the additional {@code int} property.
*/
public Builder define(String property, int value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code long} property.
*
* @param property The name of the property to define.
* @param value The {@code long} value to define for the property.
* @return A builder with the additional {@code long} property.
*/
public Builder define(String property, long value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code float} property.
*
* @param property The name of the property to define.
* @param value The {@code float} value to define for the property.
* @return A builder with the additional {@code float} property.
*/
public Builder define(String property, float value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code double} property.
*
* @param property The name of the property to define.
* @param value The {@code double} value to define for the property.
* @return A builder with the additional {@code double} property.
*/
public Builder define(String property, double value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@link java.lang.String} property.
*
* @param property The name of the property to define.
* @param value The {@link java.lang.String} value to define for the property.
* @return A builder with the additional {@link java.lang.String} property.
*/
public Builder define(String property, String value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code boolean} array property.
*
* @param property The name of the property to define.
* @param value The {@code boolean} values to define for the property.
* @return A builder with the additional {@code boolean} array property.
*/
public Builder defineArray(String property, boolean... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code byte} array property.
*
* @param property The name of the property to define.
* @param value The {@code byte} values to define for the property.
* @return A builder with the additional {@code byte} array property.
*/
public Builder defineArray(String property, byte... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code char} array property.
*
* @param property The name of the property to define.
* @param value The {@code char} values to define for the property.
* @return A builder with the additional {@code char} array property.
*/
public Builder defineArray(String property, char... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code short} array property.
*
* @param property The name of the property to define.
* @param value The {@code short} values to define for the property.
* @return A builder with the additional {@code short} array property.
*/
public Builder defineArray(String property, short... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code int} array property.
*
* @param property The name of the property to define.
* @param value The {@code int} values to define for the property.
* @return A builder with the additional {@code int} array property.
*/
public Builder defineArray(String property, int... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code long} array property.
*
* @param property The name of the property to define.
* @param value The {@code long} values to define for the property.
* @return A builder with the additional {@code long} array property.
*/
public Builder defineArray(String property, long... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code float} array property.
*
* @param property The name of the property to define.
* @param value The {@code float} values to define for the property.
* @return A builder with the additional {@code float} array property.
*/
public Builder defineArray(String property, float... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code double} array property.
*
* @param property The name of the property to define.
* @param value The {@code double} values to define for the property.
* @return A builder with the additional {@code double} array property.
*/
public Builder defineArray(String property, double... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@link java.lang.String} array property.
*
* @param property The name of the property to define.
* @param value The {@link java.lang.String} array value to define for the property.
* @return A builder with the additional {@link java.lang.String} array property.
*/
public Builder defineArray(String property, String... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Creates an annotation description for the values that were defined for this builder. It is validated that all
* properties are defined if no default value is set for an annotation property.
*
* @return An appropriate annotation description.
*/
public AnnotationDescription build() {
for (MethodDescription.InDefinedShape methodDescription : annotationType.getDeclaredMethods()) {
AnnotationValue, ?> annotationValue = annotationValues.get(methodDescription.getName());
if (annotationValue == null && methodDescription.getDefaultValue() == null) {
throw new IllegalStateException("No value or default value defined for " + methodDescription.getName());
} else if (annotationValue != null && annotationValue.filter(methodDescription).getState() != AnnotationValue.State.RESOLVED) {
throw new IllegalStateException("Illegal annotation value for " + methodDescription + ": " + annotationValue);
}
}
return new Latent(annotationType, annotationValues);
}
/**
* Creates an annotation description for the values that were defined for this builder.
*
* @param validated {@code true} if the annotation description should be validated for having included all values.
* @return An appropriate annotation description.
*/
public AnnotationDescription build(boolean validated) {
return validated
? build()
: new Latent(annotationType, annotationValues);
}
}
}