org.testifyproject.bytebuddy.implementation.attribute.AnnotationAppender Maven / Gradle / Ivy
The newest version!
package org.testifyproject.bytebuddy.implementation.attribute;
import lombok.EqualsAndHashCode;
import org.testifyproject.bytebuddy.description.annotation.AnnotationDescription;
import org.testifyproject.bytebuddy.description.enumeration.EnumerationDescription;
import org.testifyproject.bytebuddy.description.method.MethodDescription;
import org.testifyproject.bytebuddy.description.type.TypeDescription;
import org.testifyproject.bytebuddy.description.type.TypeList;
import org.testifyproject.bytebuddy.jar.asm.*;
import java.lang.reflect.Array;
import java.util.List;
/**
* Annotation appenders are capable of writing annotations to a specified target.
*/
public interface AnnotationAppender {
/**
* A constant for informing ASM over ignoring a given name.
*/
String NO_NAME = null;
/**
* Writes the given annotation to the target that this appender represents.
*
* @param annotationDescription The annotation to be written.
* @param annotationValueFilter The annotation value filter to use.
* @return Usually {@code this} or any other annotation appender capable of writing another annotation to the specified target.
*/
AnnotationAppender append(AnnotationDescription annotationDescription, AnnotationValueFilter annotationValueFilter);
/**
* Writes the given type annotation to the target that this appender represents.
*
* @param annotationDescription The annotation to be written.
* @param annotationValueFilter The annotation value filter to use.
* @param typeReference The type variable's type reference.
* @param typePath The type variable's type path.
* @return Usually {@code this} or any other annotation appender capable of writing another annotation to the specified target.
*/
AnnotationAppender append(AnnotationDescription annotationDescription, AnnotationValueFilter annotationValueFilter, int typeReference, String typePath);
/**
* Represents a target for an annotation writing process.
*/
interface Target {
/**
* Creates an annotation visitor for writing the specified annotation.
*
* @param annotationTypeDescriptor The type descriptor for the annotation to be written.
* @param visible {@code true} if the annotation is to be visible at runtime.
* @return An annotation visitor for consuming the specified annotation.
*/
AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible);
/**
* Creates an annotation visitor for writing the specified type annotation.
*
* @param annotationTypeDescriptor The type descriptor for the annotation to be written.
* @param visible {@code true} if the annotation is to be visible at runtime.
* @param typeReference The type annotation's type reference.
* @param typePath The type annotation's type path.
* @return An annotation visitor for consuming the specified annotation.
*/
AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible, int typeReference, String typePath);
/**
* Target for an annotation that is written to a Java type.
*/
@EqualsAndHashCode
class OnType implements Target {
/**
* The class visitor to write the annotation to.
*/
private final ClassVisitor classVisitor;
/**
* Creates a new wrapper for a Java type.
*
* @param classVisitor The ASM class visitor to which the annotations are appended to.
*/
public OnType(ClassVisitor classVisitor) {
this.classVisitor = classVisitor;
}
@Override
public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible) {
return classVisitor.visitAnnotation(annotationTypeDescriptor, visible);
}
@Override
public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible, int typeReference, String typePath) {
return classVisitor.visitTypeAnnotation(typeReference, TypePath.fromString(typePath), annotationTypeDescriptor, visible);
}
}
/**
* Target for an annotation that is written to a Java method or constructor.
*/
@EqualsAndHashCode
class OnMethod implements Target {
/**
* The method visitor to write the annotation to.
*/
private final MethodVisitor methodVisitor;
/**
* Creates a new wrapper for a Java method or constructor.
*
* @param methodVisitor The ASM method visitor to which the annotations are appended to.
*/
public OnMethod(MethodVisitor methodVisitor) {
this.methodVisitor = methodVisitor;
}
@Override
public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible) {
return methodVisitor.visitAnnotation(annotationTypeDescriptor, visible);
}
@Override
public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible, int typeReference, String typePath) {
return methodVisitor.visitTypeAnnotation(typeReference, TypePath.fromString(typePath), annotationTypeDescriptor, visible);
}
}
/**
* Target for an annotation that is written to a Java method or constructor parameter.
*/
@EqualsAndHashCode
class OnMethodParameter implements Target {
/**
* The method visitor to write the annotation to.
*/
private final MethodVisitor methodVisitor;
/**
* The method parameter index to write the annotation to.
*/
private final int parameterIndex;
/**
* Creates a new wrapper for a Java method or constructor.
*
* @param methodVisitor The ASM method visitor to which the annotations are appended to.
* @param parameterIndex The index of the method parameter.
*/
public OnMethodParameter(MethodVisitor methodVisitor, int parameterIndex) {
this.methodVisitor = methodVisitor;
this.parameterIndex = parameterIndex;
}
@Override
public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible) {
return methodVisitor.visitParameterAnnotation(parameterIndex, annotationTypeDescriptor, visible);
}
@Override
public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible, int typeReference, String typePath) {
return methodVisitor.visitTypeAnnotation(typeReference, TypePath.fromString(typePath), annotationTypeDescriptor, visible);
}
}
/**
* Target for an annotation that is written to a Java field.
*/
@EqualsAndHashCode
class OnField implements Target {
/**
* The field visitor to write the annotation to.
*/
private final FieldVisitor fieldVisitor;
/**
* Creates a new wrapper for a Java field.
*
* @param fieldVisitor The ASM field visitor to which the annotations are appended to.
*/
public OnField(FieldVisitor fieldVisitor) {
this.fieldVisitor = fieldVisitor;
}
@Override
public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible) {
return fieldVisitor.visitAnnotation(annotationTypeDescriptor, visible);
}
@Override
public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible, int typeReference, String typePath) {
return fieldVisitor.visitTypeAnnotation(typeReference, TypePath.fromString(typePath), annotationTypeDescriptor, visible);
}
}
}
/**
* A default implementation for an annotation appender that writes annotations to a given byte consumer
* represented by an ASM {@link org.testifyproject.bytebuddy.jar.asm.AnnotationVisitor}.
*/
@EqualsAndHashCode
class Default implements AnnotationAppender {
/**
* The target onto which an annotation write process is to be applied.
*/
private final Target target;
/**
* Creates a default annotation appender.
*
* @param target The target to which annotations are written to.
*/
public Default(Target target) {
this.target = target;
}
/**
* Handles the writing of a single annotation to an annotation visitor.
*
* @param annotationVisitor The annotation visitor the write process is to be applied on.
* @param annotation The annotation to be written.
* @param annotationValueFilter The value filter to apply for discovering which values of an annotation should be written.
*/
private static void handle(AnnotationVisitor annotationVisitor, AnnotationDescription annotation, AnnotationValueFilter annotationValueFilter) {
for (MethodDescription.InDefinedShape methodDescription : annotation.getAnnotationType().getDeclaredMethods()) {
if (annotationValueFilter.isRelevant(annotation, methodDescription)) {
apply(annotationVisitor, methodDescription.getReturnType().asErasure(), methodDescription.getName(), annotation.getValue(methodDescription).resolve());
}
}
annotationVisitor.visitEnd();
}
/**
* Performs the writing of a given annotation value to an annotation visitor.
*
* @param annotationVisitor The annotation visitor the write process is to be applied on.
* @param valueType The type of the annotation value.
* @param name The name of the annotation type.
* @param value The annotation's value.
*/
public static void apply(AnnotationVisitor annotationVisitor, TypeDescription valueType, String name, Object value) {
if (valueType.isArray()) { // The Android emulator reads annotation arrays as annotation types. Therefore, this check needs to come first.
AnnotationVisitor arrayVisitor = annotationVisitor.visitArray(name);
int length = Array.getLength(value);
TypeDescription componentType = valueType.getComponentType();
for (int index = 0; index < length; index++) {
apply(arrayVisitor, componentType, NO_NAME, Array.get(value, index));
}
arrayVisitor.visitEnd();
} else if (valueType.isAnnotation()) {
handle(annotationVisitor.visitAnnotation(name, valueType.getDescriptor()), (AnnotationDescription) value, AnnotationValueFilter.Default.APPEND_DEFAULTS);
} else if (valueType.isEnum()) {
annotationVisitor.visitEnum(name, valueType.getDescriptor(), ((EnumerationDescription) value).getValue());
} else if (valueType.represents(Class.class)) {
annotationVisitor.visit(name, Type.getType(((TypeDescription) value).getDescriptor()));
} else {
annotationVisitor.visit(name, value);
}
}
@Override
public AnnotationAppender append(AnnotationDescription annotationDescription, AnnotationValueFilter annotationValueFilter) {
switch (annotationDescription.getRetention()) {
case RUNTIME:
doAppend(annotationDescription, true, annotationValueFilter);
break;
case CLASS:
doAppend(annotationDescription, false, annotationValueFilter);
break;
case SOURCE:
break;
default:
throw new IllegalStateException("Unexpected retention policy: " + annotationDescription.getRetention());
}
return this;
}
/**
* Tries to append a given annotation by reflectively reading an annotation.
*
* @param annotation The annotation to be written.
* @param visible {@code true} if this annotation should be treated as visible at runtime.
* @param annotationValueFilter The annotation value filter to apply.
*/
private void doAppend(AnnotationDescription annotation, boolean visible, AnnotationValueFilter annotationValueFilter) {
handle(target.visit(annotation.getAnnotationType().getDescriptor(), visible), annotation, annotationValueFilter);
}
@Override
public AnnotationAppender append(AnnotationDescription annotationDescription, AnnotationValueFilter annotationValueFilter, int typeReference, String typePath) {
switch (annotationDescription.getRetention()) {
case RUNTIME:
doAppend(annotationDescription, true, annotationValueFilter, typeReference, typePath);
break;
case CLASS:
doAppend(annotationDescription, false, annotationValueFilter, typeReference, typePath);
break;
case SOURCE:
break;
default:
throw new IllegalStateException("Unexpected retention policy: " + annotationDescription.getRetention());
}
return this;
}
/**
* Tries to append a given annotation by reflectively reading an annotation.
*
* @param annotation The annotation to be written.
* @param visible {@code true} if this annotation should be treated as visible at runtime.
* @param annotationValueFilter The annotation value filter to apply.
* @param typeReference The type annotation's type reference.
* @param typePath The type annotation's type path.
*/
private void doAppend(AnnotationDescription annotation,
boolean visible,
AnnotationValueFilter annotationValueFilter,
int typeReference,
String typePath) {
handle(target.visit(annotation.getAnnotationType().getDescriptor(), visible, typeReference, typePath), annotation, annotationValueFilter);
}
}
/**
* A type visitor that visits all type annotations of a generic type and writes any discovered annotation to a
* supplied {@link AnnotationAppender}.
*/
@EqualsAndHashCode
class ForTypeAnnotations implements TypeDescription.Generic.Visitor {
/**
* Indicates that type variables type annotations are written on a Java type.
*/
public static final boolean VARIABLE_ON_TYPE = true;
/**
* Indicates that type variables type annotations are written on a Java method or constructor.
*/
public static final boolean VARIABLE_ON_INVOKEABLE = false;
/**
* Represents an empty type path.
*/
private static final String EMPTY_TYPE_PATH = "";
/**
* Represents a step to a component type within a type path.
*/
private static final char COMPONENT_TYPE_PATH = '[';
/**
* Represents a wildcard type step within a type path.
*/
private static final char WILDCARD_TYPE_PATH = '*';
/**
* Represents a (reversed) type step to an inner class within a type path.
*/
private static final char INNER_CLASS_PATH = '.';
/**
* Represents an index tzpe delimiter within a type path.
*/
private static final char INDEXED_TYPE_DELIMITER = ';';
/**
* The index that indicates that super type type annotations are written onto a super class.
*/
private static final int SUPER_CLASS_INDEX = -1;
/**
* The annotation appender to use.
*/
private final AnnotationAppender annotationAppender;
/**
* The annotation value filter to use.
*/
private final AnnotationValueFilter annotationValueFilter;
/**
* The type reference to use.
*/
private final int typeReference;
/**
* The type path to use.
*/
private final String typePath;
/**
* Creates a new type annotation appending visitor for an empty type path.
*
* @param annotationAppender The annotation appender to use.
* @param annotationValueFilter The annotation value filter to use.
* @param typeReference The type reference to use.
*/
protected ForTypeAnnotations(AnnotationAppender annotationAppender, AnnotationValueFilter annotationValueFilter, TypeReference typeReference) {
this(annotationAppender, annotationValueFilter, typeReference.getValue(), EMPTY_TYPE_PATH);
}
/**
* Creates a new type annotation appending visitor.
*
* @param annotationAppender The annotation appender to use.
* @param annotationValueFilter The annotation value filter to use.
* @param typeReference The type reference to use.
* @param typePath The type path to use.
*/
protected ForTypeAnnotations(AnnotationAppender annotationAppender, AnnotationValueFilter annotationValueFilter, int typeReference, String typePath) {
this.annotationAppender = annotationAppender;
this.annotationValueFilter = annotationValueFilter;
this.typeReference = typeReference;
this.typePath = typePath;
}
/**
* Creates a type annotation appender for a type annotations of a super class type.
*
* @param annotationAppender The annotation appender to write any type annotation to.
* @param annotationValueFilter The annotation value filter to apply.
* @return A visitor for appending type annotations of a super class.
*/
public static TypeDescription.Generic.Visitor ofSuperClass(AnnotationAppender annotationAppender,
AnnotationValueFilter annotationValueFilter) {
return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newSuperTypeReference(SUPER_CLASS_INDEX));
}
/**
* Creates a type annotation appender for type annotations of an interface type.
*
* @param annotationAppender The annotation appender to write any type annotation to.
* @param annotationValueFilter The annotation value filter to apply.
* @param index The index of the interface type.
* @return A visitor for appending type annotations of an interface type.
*/
public static TypeDescription.Generic.Visitor ofInterfaceType(AnnotationAppender annotationAppender,
AnnotationValueFilter annotationValueFilter,
int index) {
return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newSuperTypeReference(index));
}
/**
* Creates a type annotation appender for type annotations of a field's type.
*
* @param annotationAppender The annotation appender to write any type annotation to.
* @param annotationValueFilter The annotation value filter to apply.
* @return A visitor for appending type annotations of a field's type.
*/
public static TypeDescription.Generic.Visitor ofFieldType(AnnotationAppender annotationAppender,
AnnotationValueFilter annotationValueFilter) {
return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newTypeReference(TypeReference.FIELD));
}
/**
* Creates a type annotation appender for type annotations of a method's return type.
*
* @param annotationAppender The annotation appender to write any type annotation to.
* @param annotationValueFilter The annotation value filter to apply.
* @return A visitor for appending type annotations of a method's return type.
*/
public static TypeDescription.Generic.Visitor ofMethodReturnType(AnnotationAppender annotationAppender,
AnnotationValueFilter annotationValueFilter) {
return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newTypeReference(TypeReference.METHOD_RETURN));
}
/**
* Creates a type annotation appender for type annotations of a method's parameter type.
*
* @param annotationAppender The annotation appender to write any type annotation to.
* @param annotationValueFilter The annotation value filter to apply.
* @param index The parameter index.
* @return A visitor for appending type annotations of a method's parameter type.
*/
public static TypeDescription.Generic.Visitor ofMethodParameterType(AnnotationAppender annotationAppender,
AnnotationValueFilter annotationValueFilter,
int index) {
return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newFormalParameterReference(index));
}
/**
* Creates a type annotation appender for type annotations of a method's exception type.
*
* @param annotationAppender The annotation appender to write any type annotation to.
* @param annotationValueFilter The annotation value filter to apply.
* @param index The exception type's index.
* @return A visitor for appending type annotations of a method's exception type.
*/
public static TypeDescription.Generic.Visitor ofExceptionType(AnnotationAppender annotationAppender,
AnnotationValueFilter annotationValueFilter,
int index) {
return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newExceptionReference(index));
}
/**
* Creates a type annotation appender for type annotations of a method's receiver type.
*
* @param annotationAppender The annotation appender to write any type annotation to.
* @param annotationValueFilter The annotation value filter to apply.
* @return A visitor for appending type annotations of a method's receiver type.
*/
public static TypeDescription.Generic.Visitor ofReceiverType(AnnotationAppender annotationAppender,
AnnotationValueFilter annotationValueFilter) {
return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newTypeReference(TypeReference.METHOD_RECEIVER));
}
/**
* Appends all supplied type variables to the supplied method appender.
*
* @param annotationAppender The annotation appender to write any type annotation to.
* @param annotationValueFilter The annotation value filter to apply.
* @param variableOnType {@code true} if the type variables are declared by a type, {@code false} if they are declared by a method.
* @param typeVariables The type variables to append.
* @return The resulting annotation appender.
*/
public static AnnotationAppender ofTypeVariable(AnnotationAppender annotationAppender,
AnnotationValueFilter annotationValueFilter,
boolean variableOnType,
List extends TypeDescription.Generic> typeVariables) {
return ofTypeVariable(annotationAppender, annotationValueFilter, variableOnType, 0, typeVariables);
}
/**
* Appends all supplied type variables to the supplied method appender.
*
* @param annotationAppender The annotation appender to write any type annotation to.
* @param annotationValueFilter The annotation value filter to apply.
* @param variableOnType {@code true} if the type variables are declared by a type, {@code false} if they are declared by a method.
* @param subListIndex The index of the first type variable to append. All previous type variables are ignored.
* @param typeVariables The type variables to append.
* @return The resulting annotation appender.
*/
public static AnnotationAppender ofTypeVariable(AnnotationAppender annotationAppender,
AnnotationValueFilter annotationValueFilter,
boolean variableOnType,
int subListIndex,
List extends TypeDescription.Generic> typeVariables) {
int typeVariableIndex = subListIndex, variableBaseReference, variableBoundBaseBase;
if (variableOnType) {
variableBaseReference = TypeReference.CLASS_TYPE_PARAMETER;
variableBoundBaseBase = TypeReference.CLASS_TYPE_PARAMETER_BOUND;
} else {
variableBaseReference = TypeReference.METHOD_TYPE_PARAMETER;
variableBoundBaseBase = TypeReference.METHOD_TYPE_PARAMETER_BOUND;
}
for (TypeDescription.Generic typeVariable : typeVariables.subList(subListIndex, typeVariables.size())) {
int typeReference = TypeReference.newTypeParameterReference(variableBaseReference, typeVariableIndex).getValue();
for (AnnotationDescription annotationDescription : typeVariable.getDeclaredAnnotations()) {
annotationAppender = annotationAppender.append(annotationDescription, annotationValueFilter, typeReference, EMPTY_TYPE_PATH);
}
int boundIndex = !typeVariable.getUpperBounds().get(0).getSort().isTypeVariable() && typeVariable.getUpperBounds().get(0).isInterface()
? 1
: 0;
for (TypeDescription.Generic typeBound : typeVariable.getUpperBounds()) {
annotationAppender = typeBound.accept(new ForTypeAnnotations(annotationAppender,
annotationValueFilter,
TypeReference.newTypeParameterBoundReference(variableBoundBaseBase, typeVariableIndex, boundIndex++)));
}
typeVariableIndex++;
}
return annotationAppender;
}
@Override
public AnnotationAppender onGenericArray(TypeDescription.Generic genericArray) {
return genericArray.getComponentType().accept(new ForTypeAnnotations(apply(genericArray, typePath),
annotationValueFilter,
typeReference,
typePath + COMPONENT_TYPE_PATH));
}
@Override
public AnnotationAppender onWildcard(TypeDescription.Generic wildcard) {
TypeList.Generic lowerBounds = wildcard.getLowerBounds();
return (lowerBounds.isEmpty()
? wildcard.getUpperBounds().getOnly()
: lowerBounds.getOnly()).accept(new ForTypeAnnotations(apply(wildcard, typePath), annotationValueFilter, typeReference, typePath + WILDCARD_TYPE_PATH));
}
@Override
public AnnotationAppender onParameterizedType(TypeDescription.Generic parameterizedType) {
StringBuilder typePath = new StringBuilder(this.typePath);
for (int index = 0; index < parameterizedType.asErasure().getSegmentCount(); index++) {
typePath = typePath.append(INNER_CLASS_PATH);
}
AnnotationAppender annotationAppender = apply(parameterizedType, typePath.toString());
TypeDescription.Generic ownerType = parameterizedType.getOwnerType();
if (ownerType != null) {
annotationAppender = ownerType.accept(new ForTypeAnnotations(annotationAppender,
annotationValueFilter,
typeReference,
this.typePath));
}
int index = 0;
for (TypeDescription.Generic typeArgument : parameterizedType.getTypeArguments()) {
annotationAppender = typeArgument.accept(new ForTypeAnnotations(annotationAppender,
annotationValueFilter,
typeReference,
typePath.toString() + index++ + INDEXED_TYPE_DELIMITER));
}
return annotationAppender;
}
@Override
public AnnotationAppender onTypeVariable(TypeDescription.Generic typeVariable) {
return apply(typeVariable, typePath);
}
@Override
public AnnotationAppender onNonGenericType(TypeDescription.Generic typeDescription) {
StringBuilder typePath = new StringBuilder(this.typePath);
for (int index = 0; index < typeDescription.asErasure().getSegmentCount(); index++) {
typePath = typePath.append(INNER_CLASS_PATH);
}
AnnotationAppender annotationAppender = apply(typeDescription, typePath.toString());
if (typeDescription.isArray()) {
annotationAppender = typeDescription.getComponentType().accept(new ForTypeAnnotations(annotationAppender,
annotationValueFilter,
typeReference,
this.typePath + COMPONENT_TYPE_PATH)); // Impossible to be inner class
}
return annotationAppender;
}
/**
* Writes all annotations of the supplied type to this instance's annotation appender.
*
* @param typeDescription The type of what all annotations should be written of.
* @param typePath The type path to use.
* @return The resulting annotation appender.
*/
private AnnotationAppender apply(TypeDescription.Generic typeDescription, String typePath) {
AnnotationAppender annotationAppender = this.annotationAppender;
for (AnnotationDescription annotationDescription : typeDescription.getDeclaredAnnotations()) {
annotationAppender = annotationAppender.append(annotationDescription, annotationValueFilter, typeReference, typePath);
}
return annotationAppender;
}
}
}