All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.bytebuddy.description.annotation.AnnotationDescription Maven / Gradle / Ivy

There is a newer version: 1.49.0
Show 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 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 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); } } }