net.bytebuddy.description.annotation.AnnotationValue Maven / Gradle / Ivy
/*
* 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 extends W> 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 extends V> 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 extends X> 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 extends W> 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 extends Enum>> enumType;
/**
* The value for which no enumeration constant exists at runtime.
*/
private final String value;
/**
* Creates a new representation for an unknown enumeration constant of an annotation.
*
* @param enumType The loaded enumeration type.
* @param value The value for which no enumeration constant exists at runtime.
*/
public Loaded(Class extends Enum>> 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 extends AnnotationValue, ?>> 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 extends AnnotationValue, ?>> 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 extends AnnotationValue, ?>> 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 extends Annotation> type = (Class extends Annotation>) 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 extends Annotation> 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 extends Annotation> 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() + "\" */";
}
}
}
}