org.testifyproject.bytebuddy.description.annotation.AnnotationDescription Maven / Gradle / Ivy
The newest version!
package org.testifyproject.bytebuddy.description.annotation;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.EqualsAndHashCode;
import org.testifyproject.bytebuddy.description.enumeration.EnumerationDescription;
import org.testifyproject.bytebuddy.description.method.MethodDescription;
import org.testifyproject.bytebuddy.description.method.MethodList;
import org.testifyproject.bytebuddy.description.type.TypeDescription;
import org.testifyproject.bytebuddy.description.type.TypeList;
import org.testifyproject.bytebuddy.utility.privilege.SetAccessibleAction;
import java.lang.annotation.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.AccessController;
import java.util.*;
import static org.testifyproject.bytebuddy.matcher.ElementMatchers.named;
/**
* An annotation description describes {@link java.lang.annotation.Annotation} meta data of a class without this class
* being required to be loaded. All values of an annotation are therefore represented in unloaded state:
*
* - {@link java.lang.Class} instances are represented as {@link TypeDescription}s.
* - {@link java.lang.Enum} instances are represented as
* {@link org.testifyproject.bytebuddy.description.enumeration.EnumerationDescription}s.
* - {@link java.lang.annotation.Annotation}s are described as
* {@link AnnotationDescription}s.
* - All primitive types are represented as their wrapper types.
*
* An annotation can however be loaded in order to access unwrapped values. This will cause a loading of the classes
* of these values.
*/
public interface AnnotationDescription {
/**
* Indicates an inexistent annotation in a type-safe manner.
*/
AnnotationDescription.Loadable> UNDEFINED = null;
/**
* Returns the value of this annotation.
*
* @param property The property being accessed.
* @return The value for the supplied property.
*/
AnnotationValue, ?> getValue(MethodDescription.InDefinedShape property);
/**
* Returns a description of the annotation type of this annotation.
*
* @return A description of the annotation type of this annotation.
*/
TypeDescription getAnnotationType();
/**
* Links this annotation description to a given annotation type such that it can be loaded. This does not cause
* the values of this annotation to be loaded.
*
* @param annotationType The loaded annotation type of this annotation description.
* @param The type of the annotation.
* @return A loadable version of this annotation description.
*/
Loadable prepare(Class annotationType);
/**
* Returns this annotation's retention policy.
*
* @return This annotation's retention policy.
*/
RetentionPolicy getRetention();
/**
* Returns a set of all {@link ElementType}s that can declare this annotation.
*
* @return A set of all element types that can declare this annotation.
*/
Set getElementTypes();
/**
* Checks if this annotation is inherited.
*
* @return {@code true} if this annotation is inherited.
* @see Inherited
*/
boolean isInherited();
/**
* Checks if this annotation is documented.
*
* @return {@code true} if this annotation is documented.
* @see Documented
*/
boolean isDocumented();
/**
* An annotation description that is linked to a given loaded annotation type which allows its representation
* as a fully loaded instance.
*
* @param The annotation type.
*/
interface Loadable extends AnnotationDescription {
/**
* Loads this annotation description. This causes all classes referenced by the annotation value to be loaded.
* Without specifying a class loader, the annotation's class loader which was used to prepare this instance
* is used.
*
* @return A loaded version of this annotation description.
* @throws java.lang.ClassNotFoundException If any linked classes of the annotation cannot be loaded.
*/
S load() throws ClassNotFoundException;
/**
* Loads this annotation description. This causes all classes referenced by the annotation value to be loaded.
* Without specifying a class loader, the annotation's class loader which was used to prepare this instance
* is used. Any {@link java.lang.ClassNotFoundException} is wrapped in an {@link java.lang.IllegalStateException}.
*
* @return A loaded version of this annotation description.
*/
S loadSilent();
}
/**
* An {@link java.lang.reflect.InvocationHandler} for implementing annotations.
*
* @param The type of the handled annotation.
*/
class AnnotationInvocationHandler implements InvocationHandler {
/**
* The name of the {@link Object#hashCode()} method.
*/
private static final String HASH_CODE = "hashCode";
/**
* The name of the {@link Object#equals(Object)} method.
*/
private static final String EQUALS = "equals";
/**
* The name of the {@link Object#toString()} method.
*/
private static final String TO_STRING = "toString";
/**
* The loaded annotation type.
*/
private final Class extends Annotation> annotationType;
/**
* A sorted list of values of this annotation.
*/
private final LinkedHashMap> values;
/**
* Creates a new invocation handler.
*
* @param annotationType The loaded annotation type.
* @param values A sorted list of values of this annotation.
*/
protected AnnotationInvocationHandler(Class annotationType, LinkedHashMap> values) {
this.annotationType = annotationType;
this.values = values;
}
/**
* Creates a proxy instance for the supplied annotation type and values.
*
* @param classLoader The class loader that should be used for loading the annotation's values.
* @param annotationType The annotation's type.
* @param values The values that the annotation contains.
* @param The type of the handled annotation.
* @return A proxy for the annotation type and values.
* @throws ClassNotFoundException If the class of an instance that is contained by this annotation could not be found.
*/
@SuppressWarnings("unchecked")
public static S of(ClassLoader classLoader,
Class annotationType,
Map> values) throws ClassNotFoundException {
LinkedHashMap> loadedValues = new LinkedHashMap>();
for (Method method : annotationType.getDeclaredMethods()) {
AnnotationValue, ?> annotationValue = values.get(method.getName());
loadedValues.put(method, (annotationValue == null
? defaultValueOf(method)
: annotationValue).load(classLoader));
}
return (S) Proxy.newProxyInstance(classLoader, new Class>[]{annotationType}, new AnnotationInvocationHandler(annotationType, loadedValues));
}
/**
* Creates a default value for the given method.
*
* @param method The method from which to attempt the extraction of a default value.
* @return A default value representation.
*/
private static AnnotationValue, ?> defaultValueOf(Method method) {
Object defaultValue = method.getDefaultValue();
return defaultValue == null
? MissingValue.of(method)
: AnnotationDescription.ForLoadedAnnotation.asValue(defaultValue, method.getReturnType());
}
/**
* Resolves any primitive type to its wrapper type.
*
* @param type The type to resolve.
* @return The resolved type.
*/
private static Class> asWrapper(Class> type) {
if (type.isPrimitive()) {
if (type == boolean.class) {
return Boolean.class;
} else if (type == byte.class) {
return Byte.class;
} else if (type == short.class) {
return Short.class;
} else if (type == char.class) {
return Character.class;
} else if (type == int.class) {
return Integer.class;
} else if (type == long.class) {
return Long.class;
} else if (type == float.class) {
return Float.class;
} else if (type == double.class) {
return Double.class;
}
}
return type;
}
@Override
public Object invoke(Object proxy, Method method, Object[] argument) {
if (method.getDeclaringClass() != annotationType) {
if (method.getName().equals(HASH_CODE)) {
return hashCodeRepresentation();
} else if (method.getName().equals(EQUALS) && method.getParameterTypes().length == 1) {
return equalsRepresentation(proxy, argument[0]);
} else if (method.getName().equals(TO_STRING)) {
return toStringRepresentation();
} else /* if (method.getName().equals("annotationType")) */ {
return annotationType;
}
}
Object value = values.get(method).resolve();
if (!asWrapper(method.getReturnType()).isAssignableFrom(value.getClass())) {
throw new AnnotationTypeMismatchException(method, value.getClass().toString());
}
return value;
}
/**
* Returns the string representation of the represented annotation.
*
* @return The string representation of the represented annotation.
*/
protected String toStringRepresentation() {
StringBuilder toString = new StringBuilder();
toString.append('@');
toString.append(annotationType.getName());
toString.append('(');
boolean firstMember = true;
for (Map.Entry> entry : values.entrySet()) {
if (!entry.getValue().getState().isDefined()) {
continue;
}
if (firstMember) {
firstMember = false;
} else {
toString.append(", ");
}
toString.append(entry.getKey().getName())
.append('=')
.append(entry.getValue().toString());
}
toString.append(')');
return toString.toString();
}
/**
* Returns the hash code of the represented annotation.
*
* @return The hash code of the represented annotation.
*/
private int hashCodeRepresentation() {
int hashCode = 0;
for (Map.Entry> entry : values.entrySet()) {
if (!entry.getValue().getState().isDefined()) {
continue;
}
hashCode += (127 * entry.getKey().getName().hashCode()) ^ entry.getValue().hashCode();
}
return hashCode;
}
/**
* Checks if another instance is equal to this instance.
*
* @param self The annotation proxy instance.
* @param other The instance to be examined for equality to the represented instance.
* @return {@code true} if the given instance is equal to the represented instance.
*/
private boolean equalsRepresentation(Object self, Object other) {
if (self == other) {
return true;
} else if (!annotationType.isInstance(other)) {
return false;
} else if (Proxy.isProxyClass(other.getClass())) {
InvocationHandler invocationHandler = Proxy.getInvocationHandler(other);
if (invocationHandler instanceof AnnotationInvocationHandler) {
return invocationHandler.equals(this);
}
}
try {
for (Map.Entry> entry : values.entrySet()) {
try {
if (!entry.getValue().represents(entry.getKey().invoke(other))) {
return false;
}
} catch (RuntimeException exception) {
return false; // Incomplete annotations are not equal to one another.
}
}
return true;
} catch (InvocationTargetException ignored) {
return false;
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Could not access annotation property", exception);
}
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof AnnotationInvocationHandler)) return false;
AnnotationInvocationHandler that = (AnnotationInvocationHandler) other;
if (!annotationType.equals(that.annotationType)) {
return false;
}
for (Map.Entry> entry : values.entrySet()) {
if (!entry.getValue().equals(that.values.get(entry.getKey()))) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int result = annotationType.hashCode();
result = 31 * result + values.hashCode();
for (Map.Entry entry : values.entrySet()) {
result = 31 * result + entry.getValue().hashCode();
}
return result;
}
/**
* Represents a missing annotation property which is not represented by a default value.
*/
protected static class MissingValue extends AnnotationValue.Loaded.AbstractBase implements AnnotationValue {
/**
* The annotation type.
*/
private final Class extends Annotation> annotationType;
/**
* The name of the property without an annotation value.
*/
private final String property;
/**
* Creates a new representation for a missing annotation property.
*
* @param annotationType The annotation type.
* @param property The name of the property without an annotation value.
*/
protected MissingValue(Class extends Annotation> annotationType, String property) {
this.annotationType = annotationType;
this.property = property;
}
/**
* Creates a missing value for the supplied annotation property.
* @param method A method representing an annotation property.
* @return An annotation value for a missing property.
*/
@SuppressWarnings("unchecked")
protected static AnnotationValue, ?> of(Method method) {
return new MissingValue((Class extends Annotation>) method.getDeclaringClass(), method.getName());
}
@Override
public State getState() {
return State.UNDEFINED;
}
@Override
public boolean represents(Object value) {
return false;
}
@Override
public Loaded load(ClassLoader classLoader) {
return this;
}
@Override
public Loaded loadSilent(ClassLoader classLoader) {
return this;
}
@Override
public Void resolve() {
throw new IncompleteAnnotationException(annotationType, property);
}
/* does intentionally not implement hashCode, equals and toString */
}
}
/**
* An adapter implementation of an annotation.
*/
abstract class AbstractBase implements AnnotationDescription {
/**
* An array containing all element types that are a legal annotation target when such a target
* is not specified explicitly.
*/
private static final ElementType[] DEFAULT_TARGET = new ElementType[]{ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD,
ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE};
@Override
public RetentionPolicy getRetention() {
AnnotationDescription.Loadable retention = getAnnotationType().getDeclaredAnnotations().ofType(Retention.class);
return retention == null
? RetentionPolicy.CLASS
: retention.loadSilent().value();
}
@Override
public Set getElementTypes() {
AnnotationDescription.Loadable target = getAnnotationType().getDeclaredAnnotations().ofType(Target.class);
return new HashSet(Arrays.asList(target == null
? DEFAULT_TARGET
: target.loadSilent().value()));
}
@Override
public boolean isInherited() {
return getAnnotationType().getDeclaredAnnotations().isAnnotationPresent(Inherited.class);
}
@Override
public boolean isDocumented() {
return getAnnotationType().getDeclaredAnnotations().isAnnotationPresent(Documented.class);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof AnnotationDescription)) {
return false;
}
AnnotationDescription annotationDescription = ((AnnotationDescription) other);
TypeDescription annotationType = getAnnotationType();
if (!annotationDescription.getAnnotationType().equals(annotationType)) {
return false;
}
for (MethodDescription.InDefinedShape methodDescription : annotationType.getDeclaredMethods()) {
if (!getValue(methodDescription).equals(annotationDescription.getValue(methodDescription))) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int hashCode = 0;
for (MethodDescription.InDefinedShape methodDescription : getAnnotationType().getDeclaredMethods()) {
hashCode += 31 * getValue(methodDescription).hashCode();
}
return hashCode;
}
@Override
public String toString() {
TypeDescription annotationType = getAnnotationType();
StringBuilder toString = new StringBuilder().append('@').append(annotationType.getName()).append('(');
boolean firstMember = true;
for (MethodDescription.InDefinedShape methodDescription : annotationType.getDeclaredMethods()) {
if (firstMember) {
firstMember = false;
} else {
toString.append(", ");
}
toString.append(methodDescription.getName()).append('=').append(getValue(methodDescription));
}
return toString.append(')').toString();
}
/**
* An abstract implementation of a loadable annotation description.
*
* @param The annotation type this instance was prepared for.
*/
public abstract static class ForPrepared extends AbstractBase implements Loadable {
@Override
public S loadSilent() {
try {
return load();
} catch (ClassNotFoundException exception) {
throw new IllegalStateException("Could not load annotation type or referenced type", exception);
}
}
}
}
/**
* A description of an already loaded annotation.
*
* @param The type of the annotation.
*/
class ForLoadedAnnotation extends AbstractBase.ForPrepared {
/**
* The represented annotation value.
*/
private final S annotation;
/**
* The annotation's loaded type which might be loaded by a different class loader than the value's
* annotation type but must be structually equal to it.
*/
private final Class annotationType;
/**
* Creates a new annotation description for a loaded annotation.
*
* @param annotation The annotation to represent.
*/
@SuppressWarnings("unchecked")
protected ForLoadedAnnotation(S annotation) {
this(annotation, (Class) annotation.annotationType());
}
/**
* Creates a new annotation description for a loaded annotation.
*
* @param annotation The annotation to represent.
* @param annotationType The annotation's loaded type which might be loaded by a different class loader than the value's
* annotation type but must be structually equal to it.
*/
private ForLoadedAnnotation(S annotation, Class annotationType) {
this.annotation = annotation;
this.annotationType = annotationType;
}
/**
* Creates a description of the given annotation.
*
* @param annotation The annotation to be described.
* @param The type of the annotation.
* @return A description of the given annotation.
*/
public static Loadable of(U annotation) {
return new ForLoadedAnnotation(annotation);
}
@Override
public S load() throws ClassNotFoundException {
return annotationType == annotation.annotationType()
? annotation
: AnnotationInvocationHandler.of(annotationType.getClassLoader(), annotationType, asValue(annotation));
}
/**
* Extracts the annotation values of an annotation into a property map.
*
* @param annotation The annotation to convert.
* @return A mapping of property names to their annotation value.
*/
private static Map> asValue(Annotation annotation) {
Map> annotationValues = new HashMap>();
for (Method property : annotation.annotationType().getDeclaredMethods()) {
try {
annotationValues.put(property.getName(), asValue(property.invoke(annotation), property.getReturnType()));
} catch (InvocationTargetException exception) {
throw new IllegalStateException("Cannot read " + property, exception.getCause());
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Cannot access " + property, exception);
}
}
return annotationValues;
}
/**
* Transforms an annotation property to an annotation value.
*
* @param type The annotation's type.
* @param value The annotations value.
* @return An annotation value representation.
*/
@SuppressWarnings("unchecked")
public static AnnotationValue, ?> asValue(Object value, Class> type) {
// Because enums can implement annotation interfaces, the enum property needs to be checked first.
if (Enum.class.isAssignableFrom(type)) {
return AnnotationValue.ForEnumerationDescription.of(new EnumerationDescription.ForLoadedEnumeration((Enum) value));
} else if (Enum[].class.isAssignableFrom(type)) {
Enum>[] element = (Enum>[]) value;
EnumerationDescription[] enumerationDescription = new EnumerationDescription[element.length];
int index = 0;
for (Enum> anElement : element) {
enumerationDescription[index++] = new EnumerationDescription.ForLoadedEnumeration(anElement);
}
return AnnotationValue.ForDescriptionArray.of(new TypeDescription.ForLoadedType(type.getComponentType()), enumerationDescription);
} else if (Annotation.class.isAssignableFrom(type)) {
return AnnotationValue.ForAnnotationDescription.of(new TypeDescription.ForLoadedType(type), asValue((Annotation) value));
} else if (Annotation[].class.isAssignableFrom(type)) {
Annotation[] element = (Annotation[]) value;
AnnotationDescription[] annotationDescription = new AnnotationDescription[element.length];
int index = 0;
for (Annotation anElement : element) {
annotationDescription[index++] = new AnnotationDescription.Latent(new TypeDescription.ForLoadedType(type.getComponentType()), asValue(anElement));
}
return AnnotationValue.ForDescriptionArray.of(new TypeDescription.ForLoadedType(type.getComponentType()), annotationDescription);
} else if (Class.class.isAssignableFrom(type)) {
return AnnotationValue.ForTypeDescription.of(new TypeDescription.ForLoadedType((Class>) value));
} else if (Class[].class.isAssignableFrom(type)) {
Class>[] element = (Class>[]) value;
TypeDescription[] typeDescription = new TypeDescription[element.length];
int index = 0;
for (Class> anElement : element) {
typeDescription[index++] = new TypeDescription.ForLoadedType(anElement);
}
return AnnotationValue.ForDescriptionArray.of(typeDescription);
} else {
return AnnotationValue.ForConstant.of(value);
}
}
@Override
@SuppressWarnings("deprecation")
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should always be wrapped for clarity")
public AnnotationValue, ?> getValue(MethodDescription.InDefinedShape property) {
if (!property.getDeclaringType().represents(annotation.annotationType())) {
throw new IllegalArgumentException(property + " does not represent " + annotation.annotationType());
}
try {
boolean accessible = property.getDeclaringType().isPublic(); // method is required to be public
Method method = property instanceof MethodDescription.ForLoadedMethod
? ((MethodDescription.ForLoadedMethod) property).getLoadedMethod()
: null;
if (method == null || method.getDeclaringClass() != annotation.annotationType() || (!accessible && !method.isAccessible())) {
method = annotation.annotationType().getMethod(property.getName());
if (!accessible) {
AccessController.doPrivileged(new SetAccessibleAction(method));
}
}
return asValue(method.invoke(annotation), method.getReturnType());
} catch (InvocationTargetException exception) {
throw new IllegalStateException("Error reading annotation property " + property, exception.getCause());
} catch (Exception exception) {
throw new IllegalStateException("Cannot access annotation property " + property, exception);
}
}
@Override
@SuppressWarnings("unchecked")
public Loadable prepare(Class annotationType) {
if (!annotation.annotationType().getName().equals(annotationType.getName())) {
throw new IllegalArgumentException(annotationType + " does not represent " + annotation.annotationType());
}
return annotationType == annotation.annotationType()
? (Loadable) this
: new ForLoadedAnnotation((T) annotation, annotationType);
}
@Override
public TypeDescription getAnnotationType() {
return new TypeDescription.ForLoadedType(annotation.annotationType());
}
}
/**
* A latent description of an annotation value that is defined explicitly.
*/
class Latent extends AbstractBase {
/**
* The type of the annotation.
*/
private final TypeDescription annotationType;
/**
* The values of the annotation mapped by their property name.
*/
private final Map> annotationValues;
/**
* Creates a new latent annotation description.
*
* @param annotationType The type of the annotation.
* @param annotationValues The values of the annotation mapped by their property name.
*/
protected Latent(TypeDescription annotationType, Map> annotationValues) {
this.annotationType = annotationType;
this.annotationValues = annotationValues;
}
@Override
public AnnotationValue, ?> getValue(MethodDescription.InDefinedShape property) {
AnnotationValue, ?> value = annotationValues.get(property.getName());
if (value != null) {
return value;
}
AnnotationValue, ?> defaultValue = property.getDefaultValue();
if (defaultValue != null) {
return defaultValue;
}
throw new IllegalArgumentException("No value defined for: " + property);
}
@Override
public TypeDescription getAnnotationType() {
return annotationType;
}
@Override
public Loadable prepare(Class annotationType) {
if (!this.annotationType.represents(annotationType)) {
throw new IllegalArgumentException(annotationType + " does not represent " + this.annotationType);
}
return new Loadable(annotationType);
}
/**
* A loadable annotation description of a latent annotation description.
*
* @param The annotation type.
*/
protected class Loadable extends AbstractBase.ForPrepared {
/**
* The annotation type.
*/
private final Class annotationType;
/**
* Creates a loadable version of a latent annotation description.
*
* @param annotationType The annotation type.
*/
protected Loadable(Class annotationType) {
this.annotationType = annotationType;
}
@Override
public S load() throws ClassNotFoundException {
return AnnotationDescription.AnnotationInvocationHandler.of(annotationType.getClassLoader(), annotationType, annotationValues);
}
@Override
public AnnotationValue, ?> getValue(MethodDescription.InDefinedShape property) {
return Latent.this.getValue(property);
}
@Override
public TypeDescription getAnnotationType() {
return new TypeDescription.ForLoadedType(annotationType);
}
@Override
public Loadable prepare(Class annotationType) {
return Latent.this.prepare(annotationType);
}
}
}
/**
* A builder for pragmatically creating {@link org.testifyproject.bytebuddy.description.annotation.AnnotationDescription}.
*/
@EqualsAndHashCode
class Builder {
/**
* The annotation type.
*/
private final TypeDescription annotationType;
/**
* A mapping of annotation properties to their annotation values.
*/
private final Map> annotationValues;
/**
* Creates a builder for an annotation description.
*
* @param annotationType The annotation type.
* @param annotationValues A mapping of annotation properties to their annotation values.
*/
protected Builder(TypeDescription annotationType, Map> annotationValues) {
this.annotationType = annotationType;
this.annotationValues = annotationValues;
}
/**
* Creates a builder for creating an annotation of the given type.
*
* @param annotationType The annotation type.
* @return A builder for creating an annotation of the given type.
*/
public static Builder ofType(Class extends Annotation> annotationType) {
return ofType(new TypeDescription.ForLoadedType(annotationType));
}
/**
* Creates a builder for creating an annotation of the given type.
*
* @param annotationType A description of the annotation type.
* @return A builder for creating an annotation of the given type.
*/
public static Builder ofType(TypeDescription annotationType) {
if (!annotationType.isAnnotation()) {
throw new IllegalArgumentException("Not an annotation type: " + annotationType);
}
return new Builder(annotationType, Collections.>emptyMap());
}
/**
* Returns a builder with the additional, given property.
*
* @param property The name of the property to define.
* @param value An explicit description of the annotation value.
* @return A builder with the additional, given property.
*/
public Builder define(String property, AnnotationValue, ?> value) {
MethodList> methodDescriptions = annotationType.getDeclaredMethods().filter(named(property));
if (methodDescriptions.isEmpty()) {
throw new IllegalArgumentException(annotationType + " does not define a property named " + property);
} else if (!methodDescriptions.getOnly().getReturnType().asErasure().isAnnotationValue(value.resolve())) {
throw new IllegalArgumentException(value + " cannot be assigned to " + property);
}
Map> annotationValues = new HashMap>();
annotationValues.putAll(this.annotationValues);
if (annotationValues.put(methodDescriptions.getOnly().getName(), value) != null) {
throw new IllegalArgumentException("Property already defined: " + property);
}
return new Builder(annotationType, annotationValues);
}
/**
* Returns a builder with the additional enumeration property.
*
* @param property The name of the property to define.
* @param value The enumeration value to define.
* @return A builder with the additional enumeration property.
*/
public Builder define(String property, Enum> value) {
return define(property, new EnumerationDescription.ForLoadedEnumeration(value));
}
/**
* Returns a builder with the additional enumeration property.
*
* @param property The name of the property to define.
* @param enumerationType The type of the enumeration.
* @param value The enumeration value to define.
* @return A builder with the additional enumeration property.
*/
public Builder define(String property, TypeDescription enumerationType, String value) {
return define(property, new EnumerationDescription.Latent(enumerationType, value));
}
/**
* Returns a builder with the additional enumeration property.
*
* @param property The name of the property to define.
* @param value A description of the enumeration value to define.
* @return A builder with the additional enumeration property.
*/
@SuppressWarnings("unchecked")
public Builder define(String property, EnumerationDescription value) {
return define(property, AnnotationValue.ForEnumerationDescription.of(value));
}
/**
* Returns a builder with the additional annotation property.
*
* @param property The name of the property to define.
* @param annotation The annotation value to define.
* @return A builder with the additional annotation property.
*/
public Builder define(String property, Annotation annotation) {
return define(property, new ForLoadedAnnotation(annotation));
}
/**
* Returns a builder with the additional annotation property.
*
* @param property The name of the property to define.
* @param annotationDescription A description of the annotation value to define.
* @return A builder with the additional annotation property.
*/
public Builder define(String property, AnnotationDescription annotationDescription) {
return define(property, new AnnotationValue.ForAnnotationDescription(annotationDescription));
}
/**
* Returns a builder with the additional class property.
*
* @param property The name of the property to define.
* @param type The class value to define.
* @return A builder with the additional class property.
*/
public Builder define(String property, Class> type) {
return define(property, new TypeDescription.ForLoadedType(type));
}
/**
* Returns a builder with the additional class property.
*
* @param property The name of the property to define.
* @param typeDescription A description of the type to define as a property value.
* @return A builder with the additional class property.
*/
@SuppressWarnings("unchecked")
public Builder define(String property, TypeDescription typeDescription) {
return define(property, AnnotationValue.ForTypeDescription.of(typeDescription));
}
/**
* Returns a builder with the additional enumeration array property.
*
* @param property The name of the property to define.
* @param enumerationType The type of the enumeration, i.e. the component type of the enumeration array.
* @param value The enumeration values to be contained by the array.
* @param The enumeration type.
* @return A builder with the additional class property.
*/
@SuppressWarnings("unchecked")
public > Builder defineEnumerationArray(String property, Class enumerationType, T... value) {
EnumerationDescription[] enumerationDescription = new EnumerationDescription[value.length];
int index = 0;
for (T aValue : value) {
enumerationDescription[index++] = new EnumerationDescription.ForLoadedEnumeration(aValue);
}
return defineEnumerationArray(property, new TypeDescription.ForLoadedType(enumerationType), enumerationDescription);
}
/**
* Returns a builder with the additional enumeration array property.
*
* @param property The name of the property to define.
* @param enumerationType The type of the enumerations, i.e. is the component type of the enumeration array.
* @param value The enumeration values to be contained by the array.
* @return A builder with the additional enumeration property.
*/
public Builder defineEnumerationArray(String property, TypeDescription enumerationType, String... value) {
if (!enumerationType.isEnum()) {
throw new IllegalArgumentException("Not an enumeration type: " + enumerationType);
}
EnumerationDescription[] enumerationDescription = new EnumerationDescription[value.length];
for (int i = 0; i < value.length; i++) {
enumerationDescription[i] = new EnumerationDescription.Latent(enumerationType, value[i]);
}
return defineEnumerationArray(property, enumerationType, enumerationDescription);
}
/**
* Returns a builder with the additional enumeration array property.
*
* @param property The name of the property to define.
* @param enumerationType The type of the enumerations, i.e. the component type of the enumeration array.
* @param value Descriptions of the enumerations to be contained by the array.
* @return A builder with the additional enumeration property.
*/
@SuppressWarnings("unchecked")
public Builder defineEnumerationArray(String property, TypeDescription enumerationType, EnumerationDescription... value) {
return define(property, AnnotationValue.ForDescriptionArray.of(enumerationType, value));
}
/**
* Returns a builder with the additional annotation array property.
*
* @param property The name of the property to define.
* @param annotationType The type of the annotations, i.e. the component type of the enumeration array.
* @param annotation The annotation values to be contained by the array.
* @param The annotation type.
* @return A builder with the additional annotation property.
*/
@SuppressWarnings("unchecked")
public Builder defineAnnotationArray(String property, Class annotationType, T... annotation) {
return defineAnnotationArray(property,
new TypeDescription.ForLoadedType(annotationType),
new AnnotationList.ForLoadedAnnotations(annotation).toArray(new AnnotationDescription[annotation.length]));
}
/**
* Returns a builder with the additional annotation array property.
*
* @param property The name of the property to define.
* @param annotationType The type of the annotations, i.e. the component type of the enumeration array.
* @param annotationDescription Descriptions of the annotation values to be contained by the array.
* @return A builder with the additional annotation property.
*/
public Builder defineAnnotationArray(String property, TypeDescription annotationType, AnnotationDescription... annotationDescription) {
return define(property, AnnotationValue.ForDescriptionArray.of(annotationType, annotationDescription));
}
/**
* Returns a builder with the additional type array property.
*
* @param property The name of the property to define.
* @param type The types that should be contained by the array.
* @return A builder with the additional type array property.
*/
public Builder defineTypeArray(String property, Class>... type) {
return defineTypeArray(property, new TypeList.ForLoadedTypes(type).toArray(new TypeDescription[type.length]));
}
/**
* Returns a builder with the additional type array property.
*
* @param property The name of the property to define.
* @param typeDescription Descriptions of the types that should be contained by the array.
* @return A builder with the additional type array property.
*/
@SuppressWarnings("unchecked")
public Builder defineTypeArray(String property, TypeDescription... typeDescription) {
return define(property, AnnotationValue.ForDescriptionArray.of(typeDescription));
}
/**
* Returns a builder with the additional {@code boolean} property.
*
* @param property The name of the property to define.
* @param value The {@code boolean} value to define for the property.
* @return A builder with the additional {@code boolean} property.
*/
public Builder define(String property, boolean value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code byte} property.
*
* @param property The name of the property to define.
* @param value The {@code byte} value to define for the property.
* @return A builder with the additional {@code byte} property.
*/
public Builder define(String property, byte value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code char} property.
*
* @param property The name of the property to define.
* @param value The {@code char} value to define for the property.
* @return A builder with the additional {@code char} property.
*/
public Builder define(String property, char value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code short} property.
*
* @param property The name of the property to define.
* @param value The {@code short} value to define for the property.
* @return A builder with the additional {@code short} property.
*/
public Builder define(String property, short value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code int} property.
*
* @param property The name of the property to define.
* @param value The {@code int} value to define for the property.
* @return A builder with the additional {@code int} property.
*/
public Builder define(String property, int value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code long} property.
*
* @param property The name of the property to define.
* @param value The {@code long} value to define for the property.
* @return A builder with the additional {@code long} property.
*/
public Builder define(String property, long value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code float} property.
*
* @param property The name of the property to define.
* @param value The {@code float} value to define for the property.
* @return A builder with the additional {@code float} property.
*/
public Builder define(String property, float value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code double} property.
*
* @param property The name of the property to define.
* @param value The {@code double} value to define for the property.
* @return A builder with the additional {@code double} property.
*/
public Builder define(String property, double value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@link java.lang.String} property.
*
* @param property The name of the property to define.
* @param value The {@link java.lang.String} value to define for the property.
* @return A builder with the additional {@link java.lang.String} property.
*/
public Builder define(String property, String value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code boolean} array property.
*
* @param property The name of the property to define.
* @param value The {@code boolean} values to define for the property.
* @return A builder with the additional {@code boolean} array property.
*/
public Builder defineArray(String property, boolean... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code byte} array property.
*
* @param property The name of the property to define.
* @param value The {@code byte} values to define for the property.
* @return A builder with the additional {@code byte} array property.
*/
public Builder defineArray(String property, byte... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code char} array property.
*
* @param property The name of the property to define.
* @param value The {@code char} values to define for the property.
* @return A builder with the additional {@code char} array property.
*/
public Builder defineArray(String property, char... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code short} array property.
*
* @param property The name of the property to define.
* @param value The {@code short} values to define for the property.
* @return A builder with the additional {@code short} array property.
*/
public Builder defineArray(String property, short... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code int} array property.
*
* @param property The name of the property to define.
* @param value The {@code int} values to define for the property.
* @return A builder with the additional {@code int} array property.
*/
public Builder defineArray(String property, int... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code long} array property.
*
* @param property The name of the property to define.
* @param value The {@code long} values to define for the property.
* @return A builder with the additional {@code long} array property.
*/
public Builder defineArray(String property, long... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code float} array property.
*
* @param property The name of the property to define.
* @param value The {@code float} values to define for the property.
* @return A builder with the additional {@code float} array property.
*/
public Builder defineArray(String property, float... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@code double} array property.
*
* @param property The name of the property to define.
* @param value The {@code double} values to define for the property.
* @return A builder with the additional {@code double} array property.
*/
public Builder defineArray(String property, double... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Returns a builder with the additional {@link java.lang.String} array property.
*
* @param property The name of the property to define.
* @param value The {@link java.lang.String} array value to define for the property.
* @return A builder with the additional {@link java.lang.String} array property.
*/
public Builder defineArray(String property, String... value) {
return define(property, AnnotationValue.ForConstant.of(value));
}
/**
* Creates an annotation description for the values that were defined for this builder.
*
* @return An appropriate annotation description.
*/
public AnnotationDescription build() {
for (MethodDescription methodDescription : annotationType.getDeclaredMethods()) {
if (annotationValues.get(methodDescription.getName()) == null && methodDescription.getDefaultValue() == null) {
throw new IllegalStateException("No value or default value defined for " + methodDescription.getName());
}
}
return new Latent(annotationType, annotationValues);
}
}
}