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

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

Go to download

Byte Buddy is a Java library for creating Java classes at run time. This artifact is a build of Byte Buddy with a remaining dependency onto ASM. You should never depend on this module without repackaging Byte Buddy and ASM into your own namespace.

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.CachedReturnPlugin;
import net.bytebuddy.description.enumeration.EnumerationDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.utility.nullability.AlwaysNull;
import net.bytebuddy.utility.nullability.MaybeNull;

import java.lang.annotation.Annotation;
import java.lang.annotation.AnnotationTypeMismatchException;
import java.lang.annotation.IncompleteAnnotationException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.*;

/**
 * Representation of an unloaded annotation value where all values represent either:
 * 
    *
  • Primitive values (as their wrappers), {@link String}s or arrays of primitive types or strings.
  • *
  • A {@link TypeDescription} or an array of such a descriptions.
  • *
  • An {@link EnumerationDescription} or an array of such a description.
  • *
  • An {@link AnnotationDescription} or an array of such a description.
  • *
* The represented values are not necessarily resolvable, i.e. can contain non-available types, unknown enumeration * constants or inconsistent annotations. * * @param The represented value's unloaded type. * @param The represented value's loaded type. */ public interface AnnotationValue { /** * An undefined annotation value. */ @AlwaysNull AnnotationValue UNDEFINED = null; /** * Returns the state of the represented annotation value. * * @return The state represented by this instance. */ State getState(); /** * Returns the property type of the annotation value. * * @return The property type of the annotation value. */ Sort getSort(); /** * Filters this annotation value as a valid value of the provided property. * * @param property The property to filter against. * @return This annotation value or a new annotation value that describes why this value is not a valid value for the supplied property. */ AnnotationValue filter(MethodDescription.InDefinedShape property); /** * Filters this annotation value as a valid value of the provided property. * * @param property The property to filter against. * @param typeDefinition The expected type. * @return This annotation value or a new annotation value that describes why this value is not a valid value for the supplied property. */ AnnotationValue filter(MethodDescription.InDefinedShape property, TypeDefinition typeDefinition); /** * Resolves the unloaded value of this annotation. The return value of this method is not defined if this annotation value is invalid. * * @return The unloaded value of this annotation. */ T resolve(); /** * Resolves the unloaded value of this annotation. The return value of this method is not defined if this annotation value is invalid. * * @param type The annotation value's unloaded type. * @param The annotation value's unloaded type. * @return The unloaded value of this annotation. */ W resolve(Class type); /** * Returns the loaded value of this annotation. * * @param classLoader The class loader for loading this value or {@code null} for using the boot loader. * @return The loaded value of this annotation. */ Loaded load(@MaybeNull ClassLoader classLoader); /** * A rendering dispatcher is responsible for resolving annotation values to {@link String} representations. */ enum RenderingDispatcher { /** * A rendering dispatcher for any VM previous to Java 9. */ LEGACY_VM('[', ']', true) { @Override public String toSourceString(char value) { return Character.toString(value); } @Override public String toSourceString(long value) { return Long.toString(value); } @Override public String toSourceString(float value) { return Float.toString(value); } @Override public String toSourceString(double value) { return Double.toString(value); } @Override public String toSourceString(String value) { return value; } @Override public String toSourceString(TypeDescription value) { return value.toString(); } }, /** * A rendering dispatcher for Java 9 onward. */ JAVA_9_CAPABLE_VM('{', '}', true) { @Override public String toSourceString(char value) { StringBuilder stringBuilder = new StringBuilder().append('\''); if (value == '\'') { stringBuilder.append("\\'"); } else { stringBuilder.append(value); } return stringBuilder.append('\'').toString(); } @Override public String toSourceString(long value) { return Math.abs(value) <= Integer.MAX_VALUE ? String.valueOf(value) : value + "L"; } @Override public String toSourceString(float value) { return Math.abs(value) <= Float.MAX_VALUE // Float.isFinite(value) ? value + "f" : (Float.isInfinite(value) ? (value < 0.0f ? "-1.0f/0.0f" : "1.0f/0.0f") : "0.0f/0.0f"); } @Override public String toSourceString(double value) { return Math.abs(value) <= Double.MAX_VALUE // Double.isFinite(value) ? Double.toString(value) : (Double.isInfinite(value) ? (value < 0.0d ? "-1.0/0.0" : "1.0/0.0") : "0.0/0.0"); } @Override public String toSourceString(String value) { return "\"" + (value.indexOf('"') == -1 ? value : value.replace("\"", "\\\"")) + "\""; } @Override public String toSourceString(TypeDescription value) { return value.getActualName() + ".class"; } }, /** * A rendering dispatcher for Java 14 onward. */ JAVA_14_CAPABLE_VM('{', '}', true) { @Override public String toSourceString(byte value) { return "(byte)0x" + Integer.toHexString(value & 0xFF); } @Override public String toSourceString(char value) { StringBuilder stringBuilder = new StringBuilder().append('\''); if (value == '\'') { stringBuilder.append("\\'"); } else { stringBuilder.append(value); } return stringBuilder.append('\'').toString(); } @Override public String toSourceString(long value) { return value + "L"; } @Override public String toSourceString(float value) { return Math.abs(value) <= Float.MAX_VALUE // Float.isFinite(value) ? value + "f" : (Float.isInfinite(value) ? (value < 0.0f ? "-1.0f/0.0f" : "1.0f/0.0f") : "0.0f/0.0f"); } @Override public String toSourceString(double value) { return Math.abs(value) <= Double.MAX_VALUE // Double.isFinite(value) ? Double.toString(value) : (Double.isInfinite(value) ? (value < 0.0d ? "-1.0/0.0" : "1.0/0.0") : "0.0/0.0"); } @Override public String toSourceString(String value) { return "\"" + (value.indexOf('"') == -1 ? value : value.replace("\"", "\\\"")) + "\""; } @Override public String toSourceString(TypeDescription value) { return value.getActualName() + ".class"; } }, /** * A rendering dispatcher for Java 17 onward. */ JAVA_17_CAPABLE_VM('{', '}', false) { @Override public String toSourceString(byte value) { return "(byte)0x" + Integer.toHexString(value & 0xFF); } @Override public String toSourceString(char value) { StringBuilder stringBuilder = new StringBuilder().append('\''); if (value == '\'') { stringBuilder.append("\\'"); } else { stringBuilder.append(value); } return stringBuilder.append('\'').toString(); } @Override public String toSourceString(long value) { return value + "L"; } @Override public String toSourceString(float value) { return Math.abs(value) <= Float.MAX_VALUE // Float.isFinite(value) ? value + "f" : (Float.isInfinite(value) ? (value < 0.0f ? "-1.0f/0.0f" : "1.0f/0.0f") : "0.0f/0.0f"); } @Override public String toSourceString(double value) { return Math.abs(value) <= Double.MAX_VALUE // Double.isFinite(value) ? Double.toString(value) : (Double.isInfinite(value) ? (value < 0.0d ? "-1.0/0.0" : "1.0/0.0") : "0.0/0.0"); } @Override public String toSourceString(String value) { return "\"" + (value.indexOf('"') == -1 ? value : value.replace("\"", "\\\"")) + "\""; } @Override public String toSourceString(TypeDescription value) { return value.getActualName() + ".class"; } @Override public String toTypeErrorString(Class type) { return type.getName(); } }, /** * A rendering dispatcher for Java 19 onward. */ JAVA_19_CAPABLE_VM('{', '}', ClassFileVersion.ofThisVm(ClassFileVersion.JAVA_V5).isLessThan(ClassFileVersion.JAVA_V17)) { @Override public String toSourceString(byte value) { return "(byte)0x" + Integer.toHexString(value & 0xFF); } @Override public String toSourceString(char value) { StringBuilder stringBuilder = new StringBuilder().append('\''); if (value == '\'') { stringBuilder.append("\\'"); } else { stringBuilder.append(value); } return stringBuilder.append('\'').toString(); } @Override public String toSourceString(long value) { return value + "L"; } @Override public String toSourceString(float value) { return Math.abs(value) <= Float.MAX_VALUE // Float.isFinite(value) ? value + "f" : (Float.isInfinite(value) ? (value < 0.0f ? "-1.0f/0.0f" : "1.0f/0.0f") : "0.0f/0.0f"); } @Override public String toSourceString(double value) { return Math.abs(value) <= Double.MAX_VALUE // Double.isFinite(value) ? Double.toString(value) : (Double.isInfinite(value) ? (value < 0.0d ? "-1.0/0.0" : "1.0/0.0") : "0.0/0.0"); } @Override public String toSourceString(String value) { return "\"" + (value.indexOf('"') == -1 ? value : value.replace("\"", "\\\"")) + "\""; } @Override public String toSourceString(TypeDescription value) { return value.getCanonicalName() + ".class"; } @Override public String toTypeErrorString(Class type) { return type.getName(); } }; /** * The prefix text for describing a mistyped array property. */ private static final String ARRAY_PREFIX = "Array with component tag: "; /** * The rendering dispatcher for the current VM. */ public static final RenderingDispatcher CURRENT; /* * Resolves the rendering dispatcher to use. */ 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_V17)) { CURRENT = RenderingDispatcher.JAVA_17_CAPABLE_VM; } else if (classFileVersion.isAtLeast(ClassFileVersion.JAVA_V14)) { CURRENT = RenderingDispatcher.JAVA_14_CAPABLE_VM; } else if (classFileVersion.isAtLeast(ClassFileVersion.JAVA_V9)) { CURRENT = RenderingDispatcher.JAVA_9_CAPABLE_VM; } else { CURRENT = RenderingDispatcher.LEGACY_VM; } } /** * The opening brace of an array {@link String} representation. */ private final char openingBrace; /** * The closing brace of an array {@link String} representation. */ private final char closingBrace; /** * If {@code true}, annotation types are represented as integer rather then character value. */ private final boolean componentAsInteger; /** * Creates a new rendering dispatcher. * * @param openingBrace The opening brace of an array {@link String} representation. * @param closingBrace The closing brace of an array {@link String} representation. * @param componentAsInteger If {@code true}, annotation types are represented as characters rather then integer values. */ RenderingDispatcher(char openingBrace, char closingBrace, boolean componentAsInteger) { this.openingBrace = openingBrace; this.closingBrace = closingBrace; this.componentAsInteger = componentAsInteger; } /** * Represents the supplied {@code boolean} value as a {@link String}. * * @param value The {@code boolean} value to render. * @return An appropriate {@link String} representation. */ public String toSourceString(boolean value) { return Boolean.toString(value); } /** * Represents the supplied {@code boolean} value as a {@link String}. * * @param value The {@code boolean} value to render. * @return An appropriate {@link String} representation. */ public String toSourceString(byte value) { return Byte.toString(value); } /** * Represents the supplied {@code short} value as a {@link String}. * * @param value The {@code short} value to render. * @return An appropriate {@link String} representation. */ public String toSourceString(short value) { return Short.toString(value); } /** * Represents the supplied {@code char} value as a {@link String}. * * @param value The {@code char} value to render. * @return An appropriate {@link String} representation. */ public abstract String toSourceString(char value); /** * Represents the supplied {@code int} value as a {@link String}. * * @param value The {@code int} value to render. * @return An appropriate {@link String} representation. */ public String toSourceString(int value) { return Integer.toString(value); } /** * Represents the supplied {@code long} value as a {@link String}. * * @param value The {@code long} value to render. * @return An appropriate {@link String} representation. */ public abstract String toSourceString(long value); /** * Represents the supplied {@code float} value as a {@link String}. * * @param value The {@code float} value to render. * @return An appropriate {@link String} representation. */ public abstract String toSourceString(float value); /** * Represents the supplied {@code double} value as a {@link String}. * * @param value The {@code double} value to render. * @return An appropriate {@link String} representation. */ public abstract String toSourceString(double value); /** * Represents the supplied {@link String} value as a {@link String}. * * @param value The {@link String} value to render. * @return An appropriate {@link String} representation. */ public abstract String toSourceString(String value); /** * Represents the supplied {@link TypeDescription} value as a {@link String}. * * @param value The {@link TypeDescription} value to render. * @return An appropriate {@link String} representation. */ public abstract String toSourceString(TypeDescription value); /** * Represents the supplied list elements as a {@link String}. * * @param values The elements to render where each element is represented by its {@link Object#toString()} representation. * @return An appropriate {@link String} representation. */ public String toSourceString(List values) { StringBuilder stringBuilder = new StringBuilder().append(openingBrace); boolean first = true; for (Object value : values) { if (first) { first = false; } else { stringBuilder.append(", "); } stringBuilder.append(value); } return stringBuilder.append(closingBrace).toString(); } /** * Resolves a string for representing an inconsistently typed array of an annotation property. * * @param sort The sort of the inconsistent property. * @return A message to describe the component property. */ public String toArrayErrorString(Sort sort) { return ARRAY_PREFIX + (componentAsInteger || !sort.isDefined() ? Integer.toString(sort.getTag()) : Character.toString((char) sort.getTag())); } /** * Resolves a type to be represented in an error message for a mismatched type. * * @param type The represented type. * @return The name to represent. */ public String toTypeErrorString(Class type) { return type.toString(); } } /** * A loaded variant of an {@link AnnotationValue}. While * implementations of this value are required to be processed successfully by a * {@link java.lang.ClassLoader} they might still be unresolved. Typical errors on loading an annotation * value are: *
    *
  • {@link java.lang.annotation.IncompleteAnnotationException}: An annotation does not define a value * even though no default value for a property is provided.
  • *
  • {@link java.lang.EnumConstantNotPresentException}: An annotation defines an unknown value for * a known enumeration.
  • *
  • {@link java.lang.annotation.AnnotationTypeMismatchException}: An annotation property is not * of the expected type.
  • *
* Implementations of this interface must implement methods for {@link Object#hashCode()} and * {@link Object#toString()} that resemble those used for the annotation values of an actual * {@link java.lang.annotation.Annotation} implementation. Also, instances must implement * {@link java.lang.Object#equals(Object)} to return {@code true} for other instances of * this interface that represent the same annotation value. * * @param The represented value's type. */ interface Loaded { /** * Returns the state of the represented loaded annotation value. * * @return The state represented by this instance. */ State getState(); /** * Resolves the value to the actual value of an annotation. Calling this method might throw a runtime * exception if this value is either not defined or not resolved. * * @return The actual annotation value represented by this instance. */ U resolve(); /** * Resolves the value to the actual value of an annotation. Calling this method might throw a runtime * exception if this value is either not defined or not resolved. * * @param type The value's loaded type. * @param The value's loaded type. * @return The actual annotation value represented by this instance. */ V resolve(Class type); /** * Verifies if this loaded value represents the supplied loaded value. * * @param value A loaded annotation value. * @return {@code true} if the supplied annotation value is represented by this annotation value. */ boolean represents(Object value); /** * An abstract base implementation of a loaded annotation value. * * @param The represented loaded type. */ abstract class AbstractBase implements Loaded { /** * {@inheritDoc} */ public X resolve(Class type) { return type.cast(resolve()); } /** * A base implementation for an unresolved property. * * @param The represented loaded type. */ public abstract static class ForUnresolvedProperty extends AbstractBase { /** * {@inheritDoc} */ public State getState() { return State.UNRESOLVED; } /** * {@inheritDoc} */ public boolean represents(Object value) { return false; } } } } /** * Represents the state of an {@link AnnotationValue}. */ enum State { /** * An undefined annotation value describes an annotation property which is missing such that * an {@link java.lang.annotation.IncompleteAnnotationException} would be thrown. */ UNDEFINED, /** * An unresolved annotation value describes an annotation property which does not represent a * valid value but an exceptional state. */ UNRESOLVED, /** * A resolved annotation value describes an annotation property with an actual value. */ RESOLVED; /** * Returns {@code true} if the related annotation value is defined, i.e. either represents * an actual value or an exceptional state. * * @return {@code true} if the related annotation value is defined. */ public boolean isDefined() { return this != UNDEFINED; } /** * Returns {@code true} if the related annotation value is resolved, i.e. represents an actual * value. * * @return {@code true} if the related annotation value is resolved. */ public boolean isResolved() { return this == RESOLVED; } } /** * Represents the sort of an {@link AnnotationValue}. */ enum Sort { /** * A {@code boolean}-typed property. */ BOOLEAN('Z'), /** * A {@code byte}-typed property. */ BYTE('B'), /** * A {@code short}-typed property. */ SHORT('S'), /** * A {@code char}-typed property. */ CHARACTER('C'), /** * An {@code int}-typed property. */ INTEGER('I'), /** * A {@code long}-typed property. */ LONG('J'), /** * A {@code float}-typed property. */ FLOAT('F'), /** * A {@code double}-typed property. */ DOUBLE('D'), /** * A {@link String}-typed property. */ STRING('s'), /** * A {@link Class}-typed property. */ TYPE('c'), /** * A {@link Enum}-typed property. */ ENUMERATION('e'), /** * A {@link Annotation}-typed property. */ ANNOTATION('@'), /** * An array-typed property. */ ARRAY('['), /** * A property without a well-defined value. */ NONE(0); /** * The property's tag. */ private final int tag; /** * Creates a new sort. * * @param tag The property's tag. */ Sort(int tag) { this.tag = tag; } /** * Resolves a sort for the provided type definition. * * @param typeDefinition The type definition to resolve. * @return The resolved sort for the provided type definition. */ public static Sort of(TypeDefinition typeDefinition) { if (typeDefinition.represents(boolean.class)) { return BOOLEAN; } else if (typeDefinition.represents(byte.class)) { return BYTE; } else if (typeDefinition.represents(short.class)) { return SHORT; } else if (typeDefinition.represents(char.class)) { return CHARACTER; } else if (typeDefinition.represents(int.class)) { return INTEGER; } else if (typeDefinition.represents(long.class)) { return LONG; } else if (typeDefinition.represents(float.class)) { return FLOAT; } else if (typeDefinition.represents(double.class)) { return DOUBLE; } else if (typeDefinition.represents(String.class)) { return STRING; } else if (typeDefinition.represents(Class.class)) { return TYPE; } else if (typeDefinition.isEnum()) { return ENUMERATION; } else if (typeDefinition.isAnnotation()) { return ANNOTATION; } else if (typeDefinition.isArray()) { return ARRAY; } else { return NONE; } } /** * Returns the property's tag. * * @return The property's tag. */ protected int getTag() { return tag; } /** * Returns {@code true} if the property is defined. * * @return {@code true} if the property is defined. */ public boolean isDefined() { return this != NONE; } } /** * An abstract base implementation of an unloaded annotation value. * * @param The represented unloaded type. * @param The represented loaded type. */ abstract class AbstractBase implements AnnotationValue { /** * {@inheritDoc} */ public W resolve(Class type) { return type.cast(resolve()); } /** * {@inheritDoc} */ public AnnotationValue filter(MethodDescription.InDefinedShape property) { return filter(property, property.getReturnType()); } } /** * Represents a primitive value, a {@link java.lang.String} or an array of the latter types. * * @param The type where primitive values are represented by their boxed type. */ class ForConstant extends AbstractBase { /** * The represented value. */ private final U value; /** * The property delegate for the value's type. */ private final PropertyDelegate propertyDelegate; /** * Creates a new constant annotation value. * * @param value The represented value. * @param propertyDelegate The property delegate for the value's type. */ protected ForConstant(U value, PropertyDelegate propertyDelegate) { this.value = value; this.propertyDelegate = propertyDelegate; } /** * Creates an annotation value for a {@code boolean} value. * * @param value The {@code boolean} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(boolean value) { return new ForConstant(value, PropertyDelegate.ForNonArrayType.BOOLEAN); } /** * Creates an annotation value for a {@code byte} value. * * @param value The {@code byte} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(byte value) { return new ForConstant(value, PropertyDelegate.ForNonArrayType.BYTE); } /** * Creates an annotation value for a {@code short} value. * * @param value The {@code short} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(short value) { return new ForConstant(value, PropertyDelegate.ForNonArrayType.SHORT); } /** * Creates an annotation value for a {@code char} value. * * @param value The {@code char} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(char value) { return new ForConstant(value, PropertyDelegate.ForNonArrayType.CHARACTER); } /** * Creates an annotation value for a {@code int} value. * * @param value The {@code int} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(int value) { return new ForConstant(value, PropertyDelegate.ForNonArrayType.INTEGER); } /** * Creates an annotation value for a {@code long} value. * * @param value The {@code long} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(long value) { return new ForConstant(value, PropertyDelegate.ForNonArrayType.LONG); } /** * Creates an annotation value for a {@code float} value. * * @param value The {@code float} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(float value) { return new ForConstant(value, PropertyDelegate.ForNonArrayType.FLOAT); } /** * Creates an annotation value for a {@code double} value. * * @param value The {@code double} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(double value) { return new ForConstant(value, PropertyDelegate.ForNonArrayType.DOUBLE); } /** * Creates an annotation value for a {@link String} value. * * @param value The {@link String} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(String value) { return new ForConstant(value, PropertyDelegate.ForNonArrayType.STRING); } /** * Creates an annotation value for a {@code boolean[]} value. * * @param value The {@code boolean[]} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(boolean... value) { return new ForConstant(value, PropertyDelegate.ForArrayType.BOOLEAN); } /** * Creates an annotation value for a {@code byte[]} value. * * @param value The {@code byte[]} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(byte... value) { return new ForConstant(value, PropertyDelegate.ForArrayType.BYTE); } /** * Creates an annotation value for a {@code short[]} value. * * @param value The {@code short[]} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(short... value) { return new ForConstant(value, PropertyDelegate.ForArrayType.SHORT); } /** * Creates an annotation value for a {@code char[]} value. * * @param value The {@code char[]} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(char... value) { return new ForConstant(value, PropertyDelegate.ForArrayType.CHARACTER); } /** * Creates an annotation value for a {@code int[]} value. * * @param value The {@code int[]} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(int... value) { return new ForConstant(value, PropertyDelegate.ForArrayType.INTEGER); } /** * Creates an annotation value for a {@code long[]} value. * * @param value The {@code long[]} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(long... value) { return new ForConstant(value, PropertyDelegate.ForArrayType.LONG); } /** * Creates an annotation value for a {@code float[]} value. * * @param value The {@code float[]} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(float... value) { return new ForConstant(value, PropertyDelegate.ForArrayType.FLOAT); } /** * Creates an annotation value for a {@code double[]} value. * * @param value The {@code double[]} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(double... value) { return new ForConstant(value, PropertyDelegate.ForArrayType.DOUBLE); } /** * Creates an annotation value for a {@code String[]} value. * * @param value The {@code String[]} value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(String... value) { return new ForConstant(value, PropertyDelegate.ForArrayType.STRING); } /** * Creates an annotation value for any constant value, i.e any primitive (wrapper) type, * any primitive array type or any {@link String} value or array. If no constant annotation * type is provided, a runtime exception is thrown. * * @param value The value to represent. * @return An appropriate annotation value. */ public static AnnotationValue of(Object value) { if (value instanceof Boolean) { return of(((Boolean) value).booleanValue()); } else if (value instanceof Byte) { return of(((Byte) value).byteValue()); } else if (value instanceof Short) { return of(((Short) value).shortValue()); } else if (value instanceof Character) { return of(((Character) value).charValue()); } else if (value instanceof Integer) { return of(((Integer) value).intValue()); } else if (value instanceof Long) { return of(((Long) value).longValue()); } else if (value instanceof Float) { return of(((Float) value).floatValue()); } else if (value instanceof Double) { return of(((Double) value).doubleValue()); } else if (value instanceof String) { return of((String) value); } else if (value instanceof boolean[]) { return of((boolean[]) value); } else if (value instanceof byte[]) { return of((byte[]) value); } else if (value instanceof short[]) { return of((short[]) value); } else if (value instanceof char[]) { return of((char[]) value); } else if (value instanceof int[]) { return of((int[]) value); } else if (value instanceof long[]) { return of((long[]) value); } else if (value instanceof float[]) { return of((float[]) value); } else if (value instanceof double[]) { return of((double[]) value); } else if (value instanceof String[]) { return of((String[]) value); } else { throw new IllegalArgumentException("Not a constant annotation value: " + value); } } /** * {@inheritDoc} */ public State getState() { return State.RESOLVED; } /** * {@inheritDoc} */ public Sort getSort() { return Sort.of(TypeDescription.ForLoadedType.of(value.getClass()).asUnboxed()); } /** * {@inheritDoc} */ public AnnotationValue filter(MethodDescription.InDefinedShape property, TypeDefinition typeDefinition) { if (typeDefinition.asErasure().asBoxed().represents(value.getClass())) { return this; } else if (value.getClass().isArray()) { return new ForMismatchedType(property, RenderingDispatcher.CURRENT.toArrayErrorString(Sort.of(TypeDescription.ForLoadedType.of(value.getClass().getComponentType())))); } else if (value instanceof Enum) { return new ForMismatchedType(property, value.getClass().getName() + '.' + ((Enum) value).name()); } else { return new ForMismatchedType(property, RenderingDispatcher.CURRENT.toTypeErrorString(value.getClass()) + '[' + value + ']'); } } /** * {@inheritDoc} */ public U resolve() { return value; } /** * {@inheritDoc} */ public AnnotationValue.Loaded load(@MaybeNull ClassLoader classLoader) { return new Loaded(value, propertyDelegate); } @Override @CachedReturnPlugin.Enhance("hashCode") public int hashCode() { return propertyDelegate.hashCode(value); } @Override public boolean equals(@MaybeNull Object other) { return this == other || other instanceof AnnotationValue && propertyDelegate.equals(value, ((AnnotationValue) other).resolve()); } @Override public String toString() { return propertyDelegate.toString(value); } /** * A property delegate for a constant annotation value. */ protected interface PropertyDelegate { /** * Copies the provided value, if it is not immutable. * * @param value The value to copy. * @param The value's type. * @return A copy of the provided instance or the provided value, if it is immutable. */ S copy(S value); /** * Computes the value's hash code. * * @param value The value for which to compute the hash code. * @return The hash code of the provided value. */ int hashCode(Object value); /** * Determines if another value is equal to a constant annotation value. * * @param self The value that is represented as a constant annotation value. * @param other Any other value for which to determine equality. * @return {@code true} if the provided value is equal to the represented value. */ boolean equals(Object self, Object other); /** * Renders the supplied value as a {@link String}. * * @param value The value to render. * @return An appropriate {@link String} representation of the provided value. */ String toString(Object value); /** * A property delegate for a non-array type. */ enum ForNonArrayType implements PropertyDelegate { /** * A property delegate for a {@code boolean} value. */ BOOLEAN { /** {@inheritDoc} */ public String toString(Object value) { return RenderingDispatcher.CURRENT.toSourceString((Boolean) value); } }, /** * A property delegate for a {@code byte} value. */ BYTE { /** {@inheritDoc} */ public String toString(Object value) { return RenderingDispatcher.CURRENT.toSourceString((Byte) value); } }, /** * A property delegate for a {@code short} value. */ SHORT { /** {@inheritDoc} */ public String toString(Object value) { return RenderingDispatcher.CURRENT.toSourceString((Short) value); } }, /** * A property delegate for a {@code char} value. */ CHARACTER { /** {@inheritDoc} */ public String toString(Object value) { return RenderingDispatcher.CURRENT.toSourceString((Character) value); } }, /** * A property delegate for a {@code int} value. */ INTEGER { /** {@inheritDoc} */ public String toString(Object value) { return RenderingDispatcher.CURRENT.toSourceString((Integer) value); } }, /** * A property delegate for a {@code long} value. */ LONG { /** {@inheritDoc} */ public String toString(Object value) { return RenderingDispatcher.CURRENT.toSourceString((Long) value); } }, /** * A property delegate for a {@code float} value. */ FLOAT { /** {@inheritDoc} */ public String toString(Object value) { return RenderingDispatcher.CURRENT.toSourceString((Float) value); } }, /** * A property delegate for a {@code double} value. */ DOUBLE { /** {@inheritDoc} */ public String toString(Object value) { return RenderingDispatcher.CURRENT.toSourceString((Double) value); } }, /** * A property delegate for a {@link String} value. */ STRING { /** {@inheritDoc} */ public String toString(Object value) { return RenderingDispatcher.CURRENT.toSourceString((String) value); } }; /** * {@inheritDoc} */ public S copy(S value) { return value; } /** * {@inheritDoc} */ public int hashCode(Object value) { return value.hashCode(); } /** * {@inheritDoc} */ public boolean equals(Object self, Object other) { return self.equals(other); } } /** * A property delegate for an array type of a constant value. */ enum ForArrayType implements PropertyDelegate { /** * A property delegate for a {@code boolean[]} value. */ BOOLEAN { @Override protected Object doCopy(Object value) { return ((boolean[]) value).clone(); } /** {@inheritDoc} */ public int hashCode(Object value) { return Arrays.hashCode((boolean[]) value); } /** {@inheritDoc} */ public boolean equals(Object self, Object other) { return other instanceof boolean[] && Arrays.equals((boolean[]) self, (boolean[]) other); } @Override protected String toString(Object array, int index) { return ForNonArrayType.BOOLEAN.toString(Array.getBoolean(array, index)); } }, /** * A property delegate for a {@code byte[]} value. */ BYTE { @Override protected Object doCopy(Object value) { return ((byte[]) value).clone(); } /** {@inheritDoc} */ public int hashCode(Object value) { return Arrays.hashCode((byte[]) value); } /** {@inheritDoc} */ public boolean equals(Object self, Object other) { return other instanceof byte[] && Arrays.equals((byte[]) self, (byte[]) other); } @Override protected String toString(Object array, int index) { return ForNonArrayType.BYTE.toString(Array.getByte(array, index)); } }, /** * A property delegate for a {@code short[]} value. */ SHORT { @Override protected Object doCopy(Object value) { return ((short[]) value).clone(); } /** {@inheritDoc} */ public int hashCode(Object value) { return Arrays.hashCode((short[]) value); } /** {@inheritDoc} */ public boolean equals(Object self, Object other) { return other instanceof short[] && Arrays.equals((short[]) self, (short[]) other); } @Override protected String toString(Object array, int index) { return ForNonArrayType.SHORT.toString(Array.getShort(array, index)); } }, /** * A property delegate for a {@code char[]} value. */ CHARACTER { @Override protected Object doCopy(Object value) { return ((char[]) value).clone(); } /** {@inheritDoc} */ public int hashCode(Object value) { return Arrays.hashCode((char[]) value); } /** {@inheritDoc} */ public boolean equals(Object self, Object other) { return other instanceof char[] && Arrays.equals((char[]) self, (char[]) other); } @Override protected String toString(Object array, int index) { return ForNonArrayType.CHARACTER.toString(Array.getChar(array, index)); } }, /** * A property delegate for a {@code int[]} value. */ INTEGER { @Override protected Object doCopy(Object value) { return ((int[]) value).clone(); } /** {@inheritDoc} */ public int hashCode(Object value) { return Arrays.hashCode((int[]) value); } /** {@inheritDoc} */ public boolean equals(Object self, Object other) { return other instanceof int[] && Arrays.equals((int[]) self, (int[]) other); } @Override protected String toString(Object array, int index) { return ForNonArrayType.INTEGER.toString(Array.getInt(array, index)); } }, /** * A property delegate for a {@code long[]} value. */ LONG { @Override protected Object doCopy(Object value) { return ((long[]) value).clone(); } /** {@inheritDoc} */ public int hashCode(Object value) { return Arrays.hashCode((long[]) value); } /** {@inheritDoc} */ public boolean equals(Object self, Object other) { return other instanceof long[] && Arrays.equals((long[]) self, (long[]) other); } @Override protected String toString(Object array, int index) { return ForNonArrayType.LONG.toString(Array.getLong(array, index)); } }, /** * A property delegate for a {@code float[]} value. */ FLOAT { @Override protected Object doCopy(Object value) { return ((float[]) value).clone(); } /** {@inheritDoc} */ public int hashCode(Object value) { return Arrays.hashCode((float[]) value); } /** {@inheritDoc} */ public boolean equals(Object self, Object other) { return other instanceof float[] && Arrays.equals((float[]) self, (float[]) other); } @Override protected String toString(Object array, int index) { return ForNonArrayType.FLOAT.toString(Array.getFloat(array, index)); } }, /** * A property delegate for a {@code double[]} value. */ DOUBLE { @Override protected Object doCopy(Object value) { return ((double[]) value).clone(); } /** {@inheritDoc} */ public int hashCode(Object value) { return Arrays.hashCode((double[]) value); } /** {@inheritDoc} */ public boolean equals(Object self, Object other) { return other instanceof double[] && Arrays.equals((double[]) self, (double[]) other); } @Override protected String toString(Object array, int index) { return ForNonArrayType.DOUBLE.toString(Array.getDouble(array, index)); } }, /** * A property delegate for a {@code String[]} value. */ STRING { @Override protected Object doCopy(Object value) { return ((String[]) value).clone(); } /** {@inheritDoc} */ public int hashCode(Object value) { return Arrays.hashCode((String[]) value); } /** {@inheritDoc} */ public boolean equals(Object self, Object other) { return other instanceof String[] && Arrays.equals((String[]) self, (String[]) other); } @Override protected String toString(Object array, int index) { return ForNonArrayType.STRING.toString(Array.get(array, index)); } }; /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public S copy(S value) { return (S) doCopy(value); } /** * Creates a copy of the provided array. * * @param value The array to copy. * @return A shallow copy of the provided array. */ protected abstract Object doCopy(Object value); /** * {@inheritDoc} */ public String toString(Object value) { List elements = new ArrayList(Array.getLength(value)); for (int index = 0; index < Array.getLength(value); index++) { elements.add(toString(value, index)); } return RenderingDispatcher.CURRENT.toSourceString(elements); } /** * Renders the array element at the specified index. * * @param array The array for which an element should be rendered. * @param index The index of the array element to render. * @return A {@link String} representation of the array element at the supplied index. */ protected abstract String toString(Object array, int index); } } /** * Represents a trivial loaded value. * * @param The annotation properties type. */ protected static class Loaded extends AnnotationValue.Loaded.AbstractBase { /** * The represented value. */ private final V value; /** * The property delegate for the value's type. */ private final PropertyDelegate propertyDelegate; /** * Creates a new loaded representation of a constant value. * * @param value The represented value. * @param propertyDelegate The property delegate for the value's type. */ protected Loaded(V value, PropertyDelegate propertyDelegate) { this.value = value; this.propertyDelegate = propertyDelegate; } /** * {@inheritDoc} */ public State getState() { return State.RESOLVED; } /** * {@inheritDoc} */ public V resolve() { return propertyDelegate.copy(value); } /** * {@inheritDoc} */ public boolean represents(Object value) { return propertyDelegate.equals(this.value, value); } @Override @CachedReturnPlugin.Enhance("hashCode") public int hashCode() { return propertyDelegate.hashCode(value); } @Override public boolean equals(@MaybeNull Object other) { if (this == other) { return true; } else if (!(other instanceof AnnotationValue.Loaded)) { return false; } AnnotationValue.Loaded annotationValue = (AnnotationValue.Loaded) other; return annotationValue.getState().isResolved() && propertyDelegate.equals(value, annotationValue.resolve()); } @Override public String toString() { return propertyDelegate.toString(value); } } } /** * A description of an {@link java.lang.annotation.Annotation} as a value of another annotation. * * @param The type of the annotation. */ class ForAnnotationDescription extends AbstractBase { /** * The annotation description that this value represents. */ private final AnnotationDescription annotationDescription; /** * Creates a new annotation value for a given annotation description. * * @param annotationDescription The annotation description that this value represents. */ public ForAnnotationDescription(AnnotationDescription annotationDescription) { this.annotationDescription = annotationDescription; } /** * Creates an annotation value instance for describing the given annotation type and values. * * @param annotationType The annotation type. * @param annotationValues The values of the annotation. * @param The type of the annotation. * @return An annotation value representing the given annotation. */ public static AnnotationValue of(TypeDescription annotationType, Map> annotationValues) { return new ForAnnotationDescription(new AnnotationDescription.Latent(annotationType, annotationValues)); } /** * {@inheritDoc} */ public State getState() { return State.RESOLVED; } /** * {@inheritDoc} */ public Sort getSort() { return Sort.ANNOTATION; } /** * {@inheritDoc} */ public AnnotationValue filter(MethodDescription.InDefinedShape property, TypeDefinition typeDefinition) { return typeDefinition.asErasure().equals(annotationDescription.getAnnotationType()) ? this : new ForMismatchedType(property, property.getReturnType().isArray() ? RenderingDispatcher.CURRENT.toArrayErrorString(Sort.ANNOTATION) : annotationDescription.toString()); } /** * {@inheritDoc} */ public AnnotationDescription resolve() { return annotationDescription; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public AnnotationValue.Loaded load(@MaybeNull ClassLoader classLoader) { try { return new Loaded(annotationDescription .prepare((Class) Class.forName(annotationDescription.getAnnotationType().getName(), false, classLoader)) .load()); } catch (ClassNotFoundException exception) { return new ForMissingType.Loaded(annotationDescription.getAnnotationType().getName(), exception); } } @Override public int hashCode() { return annotationDescription.hashCode(); } @Override public boolean equals(@MaybeNull Object other) { return this == other || other instanceof AnnotationValue && annotationDescription.equals(((AnnotationValue) other).resolve()); } @Override public String toString() { return annotationDescription.toString(); } /** * A loaded version of the described annotation. * * @param The annotation type. */ public static class Loaded extends AnnotationValue.Loaded.AbstractBase { /** * The loaded version of the represented annotation. */ private final V annotation; /** * Creates a representation of a loaded annotation. * * @param annotation The represented annotation. */ public Loaded(V annotation) { this.annotation = annotation; } /** * {@inheritDoc} */ public State getState() { return State.RESOLVED; } /** * {@inheritDoc} */ public V resolve() { return annotation; } /** * {@inheritDoc} */ public boolean represents(Object value) { return annotation.equals(value); } @Override public int hashCode() { return annotation.hashCode(); } @Override public boolean equals(@MaybeNull Object other) { if (this == other) { return true; } else if (!(other instanceof AnnotationValue.Loaded)) { return false; } AnnotationValue.Loaded annotationValue = (AnnotationValue.Loaded) other; return annotationValue.getState().isResolved() && annotation.equals(annotationValue.resolve()); } @Override public String toString() { return annotation.toString(); } } } /** * A description of an {@link java.lang.Enum} as a value of an annotation. * * @param The type of the enumeration. */ class ForEnumerationDescription> extends AbstractBase { /** * The enumeration that is represented. */ private final EnumerationDescription enumerationDescription; /** * Creates a new description of an annotation value for a given enumeration. * * @param enumerationDescription The enumeration that is to be represented. */ public ForEnumerationDescription(EnumerationDescription enumerationDescription) { this.enumerationDescription = enumerationDescription; } /** * Creates a new annotation value for the given enumeration description. * * @param value The value to represent. * @param The type of the represented enumeration. * @return An annotation value that describes the given enumeration. */ public static > AnnotationValue of(EnumerationDescription value) { return new ForEnumerationDescription(value); } /** * {@inheritDoc} */ public EnumerationDescription resolve() { return enumerationDescription; } /** * {@inheritDoc} */ public State getState() { return State.RESOLVED; } /** * {@inheritDoc} */ public Sort getSort() { return Sort.ENUMERATION; } /** * {@inheritDoc} */ public AnnotationValue filter(MethodDescription.InDefinedShape property, TypeDefinition typeDefinition) { return typeDefinition.asErasure().equals(enumerationDescription.getEnumerationType()) ? this : new ForMismatchedType(property, property.getReturnType().isArray() ? RenderingDispatcher.CURRENT.toArrayErrorString(Sort.ENUMERATION) : enumerationDescription.getEnumerationType().getName() + '.' + enumerationDescription.getValue()); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public AnnotationValue.Loaded load(@MaybeNull ClassLoader classLoader) { try { return new Loaded(enumerationDescription.load((Class) Class.forName(enumerationDescription.getEnumerationType().getName(), false, classLoader))); } catch (ClassNotFoundException exception) { return new ForMissingType.Loaded(enumerationDescription.getEnumerationType().getName(), exception); } } @Override public int hashCode() { return enumerationDescription.hashCode(); } @Override public boolean equals(@MaybeNull Object other) { return this == other || other instanceof AnnotationValue && enumerationDescription.equals(((AnnotationValue) other).resolve()); } @Override public String toString() { return enumerationDescription.toString(); } /** * A loaded representation of an enumeration value. * * @param The type of the represented enumeration. */ public static class Loaded> extends AnnotationValue.Loaded.AbstractBase { /** * The represented enumeration. */ private final V enumeration; /** * Creates a loaded version of an enumeration description. * * @param enumeration The represented enumeration. */ public Loaded(V enumeration) { this.enumeration = enumeration; } /** * {@inheritDoc} */ public State getState() { return State.RESOLVED; } /** * {@inheritDoc} */ public V resolve() { return enumeration; } /** * {@inheritDoc} */ public boolean represents(Object value) { return enumeration.equals(value); } @Override public int hashCode() { return enumeration.hashCode(); } @Override public boolean equals(@MaybeNull Object other) { if (this == other) { return true; } else if (!(other instanceof AnnotationValue.Loaded)) { return false; } AnnotationValue.Loaded annotationValue = (AnnotationValue.Loaded) other; return annotationValue.getState().isResolved() && enumeration.equals(annotationValue.resolve()); } @Override public String toString() { return enumeration.toString(); } /** *

* Represents an annotation's enumeration value for a runtime type that is not an enumeration type. *

*

* Note: Neither of {@link Object#hashCode()}, {@link Object#toString()} and * {@link java.lang.Object#equals(Object)} are implemented specifically what resembles the way * such exceptional states are represented in the Open JDK's annotation implementations. *

*/ public static class WithIncompatibleRuntimeType extends AnnotationValue.Loaded.AbstractBase> { /** * The runtime type which is not an enumeration type. */ private final Class type; /** * Creates a new representation for an incompatible runtime type. * * @param type The runtime type which is not an enumeration type. */ public WithIncompatibleRuntimeType(Class type) { this.type = type; } /** * {@inheritDoc} */ public State getState() { return State.UNRESOLVED; } /** * {@inheritDoc} */ public Enum resolve() { throw new IncompatibleClassChangeError("Not an enumeration type: " + type.getName()); } /** * {@inheritDoc} */ public boolean represents(Object value) { return false; } /* hashCode, equals and toString are intentionally not implemented */ } } /** * Represents a property with an enumeration constant that is not defined by an enumeration type. * * @param The enumerationl type. */ public static class WithUnknownConstant> extends AbstractBase { /** * A description of the enumeration type. */ private final TypeDescription typeDescription; /** * The enumeration constant value. */ private final String value; /** * Creates a property description for an enumeration value that does not exist for the enumeration type. * * @param typeDescription A description of the enumeration type. * @param value The enumeration constant value. */ public WithUnknownConstant(TypeDescription typeDescription, String value) { this.typeDescription = typeDescription; this.value = value; } /** * {@inheritDoc} */ public State getState() { return State.UNRESOLVED; } /** * {@inheritDoc} */ public Sort getSort() { return Sort.NONE; } /** * {@inheritDoc} */ public AnnotationValue filter(MethodDescription.InDefinedShape property, TypeDefinition typeDefinition) { return this; } /** * {@inheritDoc} */ public EnumerationDescription resolve() { throw new IllegalStateException(typeDescription + " does not declare enumeration constant " + value); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public AnnotationValue.Loaded load(@MaybeNull ClassLoader classLoader) { try { // Type casting to Object is required for Java 6 compilability. return (AnnotationValue.Loaded) (Object) new Loaded((Class>) Class.forName(typeDescription.getName(), false, classLoader), value); } catch (ClassNotFoundException exception) { return new ForMissingType.Loaded(typeDescription.getName(), exception); } } /* does not implement hashCode and equals method to mimic OpenJDK behavior. */ @Override public String toString() { return value + " /* Warning: constant not present! */"; } /** * Represents a property with an enumeration constant that is not defined by an enumeration type. */ public static class Loaded extends AnnotationValue.Loaded.AbstractBase.ForUnresolvedProperty> { /** * The loaded enumeration type. */ private final Class> enumType; /** * The value for which no enumeration constant exists at runtime. */ private final String value; /** * Creates a new representation for an unknown enumeration constant of an annotation. * * @param enumType The loaded enumeration type. * @param value The value for which no enumeration constant exists at runtime. */ public Loaded(Class> enumType, String value) { this.enumType = enumType; this.value = value; } /** * {@inheritDoc} */ public Enum resolve() { throw new EnumConstantNotPresentException(enumType, value); } /* does not implement hashCode and equals method to mimic OpenJDK behavior. */ @Override public String toString() { return value + " /* Warning: constant not present! */"; } } } } /** * A description of a {@link java.lang.Class} as a value of an annotation. * * @param The type of the {@link java.lang.Class} that is described. */ class ForTypeDescription> extends AbstractBase { /** * Indicates to a class loading process that class initializers are not required to be executed when loading a type. */ private static final boolean NO_INITIALIZATION = false; /** * A map of primitive types to their loaded representation. */ private static final Map> PRIMITIVE_TYPES; /* * Initializes the maps of primitive types by their description. */ static { PRIMITIVE_TYPES = new HashMap>(); for (Class type : new Class[]{boolean.class, byte.class, short.class, char.class, int.class, long.class, float.class, double.class, void.class}) { PRIMITIVE_TYPES.put(TypeDescription.ForLoadedType.of(type), type); } } /** * A description of the represented type. */ private final TypeDescription typeDescription; /** * Creates a new annotation value that represents a type. * * @param typeDescription The represented type. */ public ForTypeDescription(TypeDescription typeDescription) { this.typeDescription = typeDescription; } /** * Creates an annotation value for representing the given type. * * @param typeDescription The type to represent. * @param The represented type. * @return An annotation value that represents the given type. */ public static > AnnotationValue of(TypeDescription typeDescription) { return new ForTypeDescription(typeDescription); } /** * {@inheritDoc} */ public State getState() { return State.RESOLVED; } /** * {@inheritDoc} */ public Sort getSort() { return Sort.TYPE; } /** * {@inheritDoc} */ public AnnotationValue filter(MethodDescription.InDefinedShape property, TypeDefinition typeDefinition) { return typeDefinition.asErasure().represents(Class.class) ? this : new ForMismatchedType(property, property.getReturnType().isArray() ? RenderingDispatcher.CURRENT.toArrayErrorString(Sort.TYPE) : Class.class.getName() + '[' + typeDescription.getName() + ']'); } /** * {@inheritDoc} */ public TypeDescription resolve() { return typeDescription; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public AnnotationValue.Loaded load(@MaybeNull ClassLoader classLoader) { try { return new Loaded((U) (typeDescription.isPrimitive() ? PRIMITIVE_TYPES.get(typeDescription) : Class.forName(typeDescription.getName(), NO_INITIALIZATION, classLoader))); } catch (ClassNotFoundException exception) { return new ForMissingType.Loaded(typeDescription.getName(), exception); } } @Override public int hashCode() { return typeDescription.hashCode(); } @Override public boolean equals(@MaybeNull Object other) { return this == other || other instanceof AnnotationValue && typeDescription.equals(((AnnotationValue) other).resolve()); } @Override public String toString() { return RenderingDispatcher.CURRENT.toSourceString(typeDescription); } /** * A loaded annotation value for a given type. * * @param The represented type. */ protected static class Loaded> extends AnnotationValue.Loaded.AbstractBase { /** * The represented type. */ private final U type; /** * Creates a new loaded annotation value for a given type. * * @param type The represented type. */ public Loaded(U type) { this.type = type; } /** * {@inheritDoc} */ public State getState() { return State.RESOLVED; } /** * {@inheritDoc} */ public U resolve() { return type; } /** * {@inheritDoc} */ public boolean represents(Object value) { return type.equals(value); } @Override public int hashCode() { return type.hashCode(); } @Override public boolean equals(@MaybeNull Object other) { if (this == other) { return true; } else if (!(other instanceof AnnotationValue.Loaded)) { return false; } AnnotationValue.Loaded annotationValue = (AnnotationValue.Loaded) other; return annotationValue.getState().isResolved() && type.equals(annotationValue.resolve()); } @Override public String toString() { return RenderingDispatcher.CURRENT.toSourceString(TypeDescription.ForLoadedType.of(type)); } } } /** * Describes a complex array that is the value of an annotation. Complex arrays are arrays that might trigger the loading * of user-defined types, i.e. {@link java.lang.Class}, {@link java.lang.annotation.Annotation} and {@link java.lang.Enum} * instances. * * @param The array type of the annotation's value when it is not loaded. * @param The array type of the annotation's value when it is loaded. */ class ForDescriptionArray extends AbstractBase { /** * The component type for arrays containing unloaded versions of the annotation array's values. */ private final Class unloadedComponentType; /** * A description of the component type when it is loaded. */ private final TypeDescription componentType; /** * A list of values of the array elements. */ private final List> values; /** * Creates a new complex array. * * @param unloadedComponentType The component type for arrays containing unloaded versions of the annotation array's values. * @param componentType A description of the component type when it is loaded. * @param values A list of values of the array elements. */ public ForDescriptionArray(Class unloadedComponentType, TypeDescription componentType, List> values) { this.unloadedComponentType = unloadedComponentType; this.componentType = componentType; this.values = values; } /** * Creates a new complex array of enumeration descriptions. * * @param enumerationType A description of the type of the enumeration. * @param enumerationDescription An array of enumeration descriptions. * @param The type of the enumeration. * @return A description of the array of enumeration values. */ public static > AnnotationValue of(TypeDescription enumerationType, EnumerationDescription[] enumerationDescription) { List> values = new ArrayList>(enumerationDescription.length); for (EnumerationDescription value : enumerationDescription) { if (!value.getEnumerationType().equals(enumerationType)) { throw new IllegalArgumentException(value + " is not of " + enumerationType); } values.add(ForEnumerationDescription.of(value)); } return new ForDescriptionArray(EnumerationDescription.class, enumerationType, values); } /** * Creates a new complex array of annotation descriptions. * * @param annotationType A description of the type of the annotation. * @param annotationDescription An array of annotation descriptions. * @param The type of the annotation. * @return A description of the array of enumeration values. */ public static AnnotationValue of(TypeDescription annotationType, AnnotationDescription[] annotationDescription) { List> values = new ArrayList>(annotationDescription.length); for (AnnotationDescription value : annotationDescription) { if (!value.getAnnotationType().equals(annotationType)) { throw new IllegalArgumentException(value + " is not of " + annotationType); } values.add(new ForAnnotationDescription(value)); } return new ForDescriptionArray(AnnotationDescription.class, annotationType, values); } /** * Creates a new complex array of annotation descriptions. * * @param typeDescription A description of the types contained in the array. * @return A description of the array of enumeration values. */ @SuppressWarnings({"unchecked", "rawtypes", "cast"}) public static AnnotationValue[]> of(TypeDescription[] typeDescription) { List>> values = new ArrayList>>(typeDescription.length); for (TypeDescription value : typeDescription) { values.add((AnnotationValue) ForTypeDescription.of(value)); } return new ForDescriptionArray[]>(TypeDescription.class, TypeDescription.ForLoadedType.of(Class.class), values); } /** * {@inheritDoc} */ public State getState() { return State.RESOLVED; } /** * {@inheritDoc} */ public Sort getSort() { return Sort.ARRAY; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming component type for array type.") public AnnotationValue filter(MethodDescription.InDefinedShape property, TypeDefinition typeDefinition) { if (typeDefinition.isArray() && typeDefinition.getComponentType().asErasure().equals(componentType)) { for (AnnotationValue value : values) { if (value.getSort() != Sort.of(componentType)) { return new ForMismatchedType(property, RenderingDispatcher.CURRENT.toArrayErrorString(value.getSort())); } value = value.filter(property, typeDefinition.getComponentType()); if (value.getState() != State.RESOLVED) { return (AnnotationValue) value; } } return this; } else { return new ForMismatchedType(property, RenderingDispatcher.CURRENT.toArrayErrorString(Sort.of(componentType))); } } /** * {@inheritDoc} */ public U resolve() { @SuppressWarnings("unchecked") U resolved = (U) Array.newInstance(unloadedComponentType, values.size()); int index = 0; for (AnnotationValue value : values) { Array.set(resolved, index++, value.resolve()); } return resolved; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public AnnotationValue.Loaded load(@MaybeNull ClassLoader classLoader) { List> values = new ArrayList>(this.values.size()); for (AnnotationValue value : this.values) { values.add(value.load(classLoader)); } try { return new Loaded((Class) (componentType.isPrimitive() ? unloadedComponentType : Class.forName(componentType.getName(), false, classLoader)), values); } catch (ClassNotFoundException exception) { return new ForMissingType.Loaded(componentType.getName(), exception); } } @Override @CachedReturnPlugin.Enhance("hashCode") public int hashCode() { int result = 1; for (AnnotationValue value : values) { result = 31 * result + value.hashCode(); } return result; } @Override public boolean equals(@MaybeNull Object other) { if (this == other) { return true; } else if (!(other instanceof AnnotationValue)) { return false; } AnnotationValue annotationValue = (AnnotationValue) other; Object value = annotationValue.resolve(); if (!value.getClass().isArray()) { return false; } if (values.size() != Array.getLength(value)) { return false; } Iterator> iterator = values.iterator(); for (int index = 0; index < values.size(); index++) { AnnotationValue self = iterator.next(); if (!self.resolve().equals(Array.get(value, index))) { return false; } } return true; } @Override public String toString() { return RenderingDispatcher.CURRENT.toSourceString(values); } /** * Represents a loaded complex array. * * @param The type of the loaded array. */ protected static class Loaded extends AnnotationValue.Loaded.AbstractBase { /** * The loaded component type of the array. */ private final Class componentType; /** * A list of loaded values that the represented array contains. */ private final List> values; /** * Creates a new loaded value representing a complex array. * * @param componentType The loaded component type of the array. * @param values A list of loaded values that the represented array contains. */ protected Loaded(Class componentType, List> values) { this.componentType = componentType; this.values = values; } /** * {@inheritDoc} */ public State getState() { for (AnnotationValue.Loaded value : values) { if (!value.getState().isResolved()) { return State.UNRESOLVED; } } return State.RESOLVED; } /** * {@inheritDoc} */ public W resolve() { @SuppressWarnings("unchecked") W array = (W) Array.newInstance(componentType, values.size()); int index = 0; for (AnnotationValue.Loaded annotationValue : values) { Array.set(array, index++, annotationValue.resolve()); } return array; } /** * {@inheritDoc} */ public boolean represents(Object value) { if (!value.getClass().isArray()) return false; if (value.getClass().getComponentType() != componentType) return false; if (values.size() != Array.getLength(value)) return false; Iterator> iterator = values.iterator(); for (int index = 0; index < Array.getLength(value); index++) { AnnotationValue.Loaded self = iterator.next(); if (!self.represents(Array.get(value, index))) { return false; } } return true; } @Override @CachedReturnPlugin.Enhance("hashCode") public int hashCode() { int result = 1; for (AnnotationValue.Loaded value : values) { result = 31 * result + value.hashCode(); } return result; } @Override public boolean equals(@MaybeNull Object other) { if (this == other) { return true; } else if (!(other instanceof AnnotationValue.Loaded)) { return false; } AnnotationValue.Loaded annotationValue = (AnnotationValue.Loaded) other; if (!annotationValue.getState().isResolved()) { return false; } Object value = annotationValue.resolve(); if (!value.getClass().isArray()) { return false; } if (values.size() != Array.getLength(value)) { return false; } Iterator> iterator = values.iterator(); for (int index = 0; index < Array.getLength(value); index++) { AnnotationValue.Loaded self = iterator.next(); if (!self.getState().isResolved() || !self.resolve().equals(Array.get(value, index))) { return false; } } return true; } @Override public String toString() { return RenderingDispatcher.CURRENT.toSourceString(values); } } } /** * An annotation value for a type that could not be loaded. * * @param The type of the annotation's value when it is not loaded. * @param The type of the annotation's value when it is loaded. */ class ForMissingType extends AbstractBase { /** * The type's binary name. */ private final String typeName; /** * Creates a new annotation value for a type that cannot be loaded. * * @param typeName The type's binary name. */ public ForMissingType(String typeName) { this.typeName = typeName; } /** * {@inheritDoc} */ public State getState() { return State.UNRESOLVED; } /** * {@inheritDoc} */ public Sort getSort() { return Sort.NONE; } /** * {@inheritDoc} */ public AnnotationValue filter(MethodDescription.InDefinedShape property, TypeDefinition typeDefinition) { return this; } /** * {@inheritDoc} */ public U resolve() { throw new IllegalStateException("Type not found: " + typeName); } /** * {@inheritDoc} */ public AnnotationValue.Loaded load(@MaybeNull ClassLoader classLoader) { return new Loaded(typeName, new ClassNotFoundException(typeName)); } /* does not implement hashCode and equals method to mimic OpenJDK behavior. */ @Override public String toString() { return typeName + ".class /* Warning: type not present! */"; } /** * Represents a missing type during an annotation's resolution. * * @param The represented type. */ public static class Loaded extends AnnotationValue.Loaded.AbstractBase.ForUnresolvedProperty { /** * The type's binary name. */ private final String typeName; /** * The exception describing the missing type. */ private final ClassNotFoundException exception; /** * The type's binary name. * * @param typeName The type's binary name. * @param exception The exception describing the missing type. */ public Loaded(String typeName, ClassNotFoundException exception) { this.typeName = typeName; this.exception = exception; } /** * {@inheritDoc} */ public U resolve() { throw new TypeNotPresentException(typeName, exception); } @Override public String toString() { return typeName + ".class /* Warning: type not present! */"; } } } /** * Describes an annotation value that does not match the annotation' type for a property. * * @param The type of the annotation's value when it is not loaded. * @param The type of the annotation's value when it is loaded. */ class ForMismatchedType extends AbstractBase { /** * The property that does not defines a non-matching value. */ private final MethodDescription.InDefinedShape property; /** * A value description of the property. */ private final String value; /** * Creates an annotation description for a mismatched type. * * @param property The property that does not defines a non-matching value. * @param value A value description of the property. */ public ForMismatchedType(MethodDescription.InDefinedShape property, String value) { this.property = property; this.value = value; } /** * {@inheritDoc} */ public State getState() { return State.UNRESOLVED; } /** * {@inheritDoc} */ public Sort getSort() { return Sort.NONE; } /** * {@inheritDoc} */ public AnnotationValue filter(MethodDescription.InDefinedShape property, TypeDefinition typeDefinition) { return new ForMismatchedType(property, value); } /** * {@inheritDoc} */ public U resolve() { throw new IllegalStateException(value + " cannot be used as value for " + property); } /** * {@inheritDoc} */ public AnnotationValue.Loaded load(@MaybeNull ClassLoader classLoader) { try { Class type = Class.forName(property.getDeclaringType().getName(), false, classLoader); try { return new Loaded(type.getMethod(property.getName()), value); } catch (NoSuchMethodException exception) { return new ForIncompatibleType.Loaded(type); } } catch (ClassNotFoundException exception) { return new ForMissingType.Loaded(property.getDeclaringType().getName(), exception); } } /* does not implement hashCode and equals method to mimic OpenJDK behavior. */ @Override public String toString() { return "/* Warning type mismatch! \"" + value + "\" */"; } /** * Describes an annotation value for a property that is not assignable to it. * * @param The type of the annotation's expected value. */ public static class Loaded extends AnnotationValue.Loaded.AbstractBase.ForUnresolvedProperty { /** * The annotation property that is not well-defined. */ private final Method property; /** * A value description of the incompatible property or {@code null}. */ private final String value; /** * Creates a new loaded version of a property with an incompatible type. * * @param property The annotation property that is not well-defined. * @param value A value description of the incompatible property or {@code null}. */ public Loaded(Method property, String value) { this.property = property; this.value = value; } /** * {@inheritDoc} */ public W resolve() { throw new AnnotationTypeMismatchException(property, value); } @Override public String toString() { return "/* Warning type mismatch! \"" + value + "\" */"; } } } /** * Represents a missing annotation property which is not represented by a default value. * * @param The type of the annotation's value when it is not loaded. * @param The type of the annotation's value when it is loaded. */ class ForMissingValue extends AnnotationValue.AbstractBase { /** * The annotation type for which a property is not defined. */ private final TypeDescription typeDescription; /** * The name of the property without an annotation value. */ private final String property; /** * Creates a new missing annotation value. * * @param typeDescription The annotation type for which a property is not defined. * @param property The name of the property without an annotation value. */ public ForMissingValue(TypeDescription typeDescription, String property) { this.typeDescription = typeDescription; this.property = property; } /** * {@inheritDoc} */ public State getState() { return State.UNDEFINED; } /** * {@inheritDoc} */ public Sort getSort() { return Sort.NONE; } /** * {@inheritDoc} */ public AnnotationValue filter(MethodDescription.InDefinedShape property, TypeDefinition typeDefinition) { return this; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public AnnotationValue.Loaded load(@MaybeNull ClassLoader classLoader) { try { Class type = (Class) Class.forName(typeDescription.getName(), false, classLoader); return type.isAnnotation() ? new Loaded(type, property) : new ForIncompatibleType.Loaded(type); } catch (ClassNotFoundException exception) { return new ForMissingType.Loaded(typeDescription.getName(), exception); } } /** * {@inheritDoc} */ public U resolve() { throw new IllegalStateException(typeDescription + " does not define " + property); } /* does not implement hashCode and equals method to mimic OpenJDK behavior. Does never appear in toString methods. */ /** * Describes an annotation value for a property that is not assignable to it. * * @param The type of the annotation's expected value. */ public static class Loaded extends AnnotationValue.Loaded.AbstractBase { /** * The annotation type. */ private final Class type; /** * The name of the property for which the annotation value is missing. */ private final String property; /** * Creates a new loaded representation for an unresolved property. * * @param type The annotation type. * @param property The name of the property for which the annotation value is missing. */ public Loaded(Class type, String property) { this.type = type; this.property = property; } /** * {@inheritDoc} */ public State getState() { return State.UNDEFINED; } /** * {@inheritDoc} */ public W resolve() { throw new IncompleteAnnotationException(type, property); } /** * {@inheritDoc} */ public boolean represents(Object value) { return false; } /* does not implement hashCode and equals method to mimic OpenJDK behavior. Does never appear in toString methods. */ } } /** * Represents an annotation value where its declared type does not fulfil an expectation. * * @param The type of the annotation's value when it is not loaded. * @param The type of the annotation's value when it is loaded. */ class ForIncompatibleType extends AnnotationValue.AbstractBase { /** * A description of the type that does not fulfil an expectation. */ private final TypeDescription typeDescription; /** * Creates a new description for an annotation value that does not fulfil expectations. * * @param typeDescription A description of the type that does not fulfil the expectations. */ public ForIncompatibleType(TypeDescription typeDescription) { this.typeDescription = typeDescription; } /** * {@inheritDoc} */ public State getState() { return State.UNRESOLVED; } /** * {@inheritDoc} */ public Sort getSort() { return Sort.NONE; } /** * {@inheritDoc} */ public AnnotationValue filter(MethodDescription.InDefinedShape property, TypeDefinition typeDefinition) { return this; } /** * {@inheritDoc} */ public U resolve() { throw new IllegalStateException("Property is defined with an incompatible runtime type: " + typeDescription); } /** * {@inheritDoc} */ public AnnotationValue.Loaded load(@MaybeNull ClassLoader classLoader) { try { return new Loaded(Class.forName(typeDescription.getName(), false, classLoader)); } catch (ClassNotFoundException exception) { return new ForMissingType.Loaded(typeDescription.getName(), exception); } } /* does not implement hashCode and equals method to mimic OpenJDK behavior. */ @Override public String toString() { return "/* Warning type incompatibility! \"" + typeDescription.getName() + "\" */"; } /** * A description of annotation value for a type that does not fulfil an expectation. * * @param The type of the annotation's expected value. */ public static class Loaded extends AnnotationValue.Loaded.AbstractBase.ForUnresolvedProperty { /** * The type that does not fulfil an expectation. */ private final Class type; /** * Creates a new description of an annotation. * * @param type The type that does not fulfil an expectation. */ public Loaded(Class type) { this.type = type; } /** * {@inheritDoc} */ public W resolve() { throw new IncompatibleClassChangeError(type.toString()); } /* does not implement hashCode and equals method to mimic OpenJDK behavior. */ @Override public String toString() { return "/* Warning type incompatibility! \"" + type.getName() + "\" */"; } } } }