org.testifyproject.bytebuddy.asm.AsmVisitorWrapper Maven / Gradle / Ivy
package org.testifyproject.bytebuddy.asm;
import org.testifyproject.bytebuddy.description.field.FieldDescription;
import org.testifyproject.bytebuddy.description.method.MethodDescription;
import org.testifyproject.bytebuddy.description.type.TypeDescription;
import org.testifyproject.bytebuddy.matcher.ElementMatcher;
import org.testifyproject.bytebuddy.utility.CompoundList;
import org.testifyproject.bytebuddy.jar.asm.ClassVisitor;
import org.testifyproject.bytebuddy.jar.asm.FieldVisitor;
import org.testifyproject.bytebuddy.jar.asm.MethodVisitor;
import org.testifyproject.bytebuddy.jar.asm.Opcodes;
import java.util.*;
/**
* A class visitor wrapper is used in order to register an intermediate ASM {@link org.testifyproject.bytebuddy.jar.asm.ClassVisitor} which
* is applied to the main type created by a {@link org.testifyproject.bytebuddy.dynamic.DynamicType.Builder} but not
* to any {@link org.testifyproject.bytebuddy.implementation.auxiliary.AuxiliaryType}s, if any.
*/
public interface AsmVisitorWrapper {
/**
* Defines the flags that are provided to any {@code ClassWriter} when writing a class. Typically, this gives opportunity to instruct ASM
* to compute stack map frames or the size of the local variables array and the operand stack. If no specific flags are required for
* applying this wrapper, the given value is to be returned.
*
* @param flags The currently set flags. This value should be combined (e.g. {@code flags | foo}) into the value that is returned by this wrapper.
* @return The flags to be provided to the ASM {@code ClassWriter}.
*/
int mergeWriter(int flags);
/**
* Defines the flags that are provided to any {@code ClassReader} when reading a class if applicable. Typically, this gives opportunity to
* instruct ASM to expand or skip frames and to skip code and debug information. If no specific flags are required for applying this
* wrapper, the given value is to be returned.
*
* @param flags The currently set flags. This value should be combined (e.g. {@code flags | foo}) into the value that is returned by this wrapper.
* @return The flags to be provided to the ASM {@code ClassReader}.
*/
int mergeReader(int flags);
/**
* Applies a {@code ClassVisitorWrapper} to the creation of a {@link org.testifyproject.bytebuddy.dynamic.DynamicType}.
*
* @param instrumentedType The instrumented type.
* @param classVisitor A {@code ClassVisitor} to become the new primary class visitor to which the created
* {@link org.testifyproject.bytebuddy.dynamic.DynamicType} is written to.
* @return A new {@code ClassVisitor} that usually delegates to the {@code ClassVisitor} delivered in the argument.
*/
ClassVisitor wrap(TypeDescription instrumentedType, ClassVisitor classVisitor);
/**
* A class visitor wrapper that does not apply any changes.
*/
enum NoOp implements AsmVisitorWrapper {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public int mergeWriter(int flags) {
return flags;
}
@Override
public int mergeReader(int flags) {
return flags;
}
@Override
public ClassVisitor wrap(TypeDescription instrumentedType, ClassVisitor classVisitor) {
return classVisitor;
}
@Override
public String toString() {
return "AsmVisitorWrapper.NoOp." + name();
}
}
/**
* An abstract base implementation of an ASM visitor wrapper that does not set any flags.
*/
abstract class AbstractBase implements AsmVisitorWrapper {
@Override
public int mergeWriter(int flags) {
return flags;
}
@Override
public int mergeReader(int flags) {
return flags;
}
}
/**
* An ASM visitor wrapper that allows to wrap declared fields of the instrumented type with a {@link FieldVisitorWrapper}.
*/
class ForDeclaredFields extends AbstractBase {
/**
* The list of entries that describe matched fields in their application order.
*/
private final List entries;
/**
* Creates a new visitor wrapper for declared fields.
*/
public ForDeclaredFields() {
this(Collections.emptyList());
}
/**
* Creates a new visitor wrapper for declared fields.
*
* @param entries The list of entries that describe matched fields in their application order.
*/
protected ForDeclaredFields(List entries) {
this.entries = entries;
}
/**
* Defines a new field visitor wrapper to be applied if the given field matcher is matched. Previously defined
* entries are applied before the given matcher is applied.
*
* @param matcher The matcher to identify fields to be wrapped.
* @param fieldVisitorWrapper The field visitor wrapper to be applied if the given matcher is matched.
* @return A new ASM visitor wrapper that applied the given field visitor wrapper if the supplied matcher is matched.
*/
public ForDeclaredFields field(ElementMatcher super FieldDescription.InDefinedShape> matcher, FieldVisitorWrapper fieldVisitorWrapper) {
return new ForDeclaredFields(CompoundList.of(entries, new Entry(matcher, fieldVisitorWrapper)));
}
@Override
public ClassVisitor wrap(TypeDescription instrumentedType, ClassVisitor classVisitor) {
return new DispatchingVisitor(classVisitor, instrumentedType);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& entries.equals(((ForDeclaredFields) other).entries);
}
@Override
public int hashCode() {
return entries.hashCode();
}
@Override
public String toString() {
return "AsmVisitorWrapper.ForDeclaredFields{" +
"entries=" + entries +
'}';
}
/**
* A field visitor wrapper that allows for wrapping a {@link FieldVisitor} defining a declared field.
*/
public interface FieldVisitorWrapper {
/**
* Wraps a field visitor.
*
* @param instrumentedType The instrumented type.
* @param fieldDescription The field that is currently being defined.
* @param fieldVisitor The original field visitor that defines the given field.
* @return The wrapped field visitor.
*/
FieldVisitor wrap(TypeDescription instrumentedType, FieldDescription.InDefinedShape fieldDescription, FieldVisitor fieldVisitor);
}
/**
* An entry describing a field visitor wrapper paired with a matcher for fields to be wrapped.
*/
protected static class Entry implements ElementMatcher, FieldVisitorWrapper {
/**
* The matcher to identify fields to be wrapped.
*/
private final ElementMatcher super FieldDescription.InDefinedShape> matcher;
/**
* The field visitor wrapper to be applied if the given matcher is matched.
*/
private final FieldVisitorWrapper fieldVisitorWrapper;
/**
* Creates a new entry.
*
* @param matcher The matcher to identify fields to be wrapped.
* @param fieldVisitorWrapper The field visitor wrapper to be applied if the given matcher is matched.
*/
protected Entry(ElementMatcher super FieldDescription.InDefinedShape> matcher, FieldVisitorWrapper fieldVisitorWrapper) {
this.matcher = matcher;
this.fieldVisitorWrapper = fieldVisitorWrapper;
}
@Override
public boolean matches(FieldDescription.InDefinedShape target) {
return target != null && matcher.matches(target);
}
@Override
public FieldVisitor wrap(TypeDescription instrumentedType, FieldDescription.InDefinedShape fieldDescription, FieldVisitor fieldVisitor) {
return fieldVisitorWrapper.wrap(instrumentedType, fieldDescription, fieldVisitor);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Entry entry = (Entry) other;
return matcher.equals(entry.matcher)
&& fieldVisitorWrapper.equals(entry.fieldVisitorWrapper);
}
@Override
public int hashCode() {
int result = matcher.hashCode();
result = 31 * result + fieldVisitorWrapper.hashCode();
return result;
}
@Override
public String toString() {
return "AsmVisitorWrapper.ForDeclaredFields.Entry{" +
"matcher=" + matcher +
", fieldVisitorWrapper=" + fieldVisitorWrapper +
'}';
}
}
/**
* A class visitor that applies the outer ASM visitor for identifying declared fields.
*/
protected class DispatchingVisitor extends ClassVisitor {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* A mapping of fields by their name.
*/
private final Map fieldsByName;
/**
* Creates a new dispatching visitor.
*
* @param classVisitor The underlying class visitor.
* @param instrumentedType The instrumented type.
*/
protected DispatchingVisitor(ClassVisitor classVisitor, TypeDescription instrumentedType) {
super(Opcodes.ASM5, classVisitor);
this.instrumentedType = instrumentedType;
fieldsByName = new HashMap();
for (FieldDescription.InDefinedShape fieldDescription : instrumentedType.getDeclaredFields()) {
fieldsByName.put(fieldDescription.getInternalName(), fieldDescription);
}
}
@Override
public FieldVisitor visitField(int modifiers, String internalName, String descriptor, String signature, Object defaultValue) {
FieldVisitor fieldVisitor = super.visitField(modifiers, internalName, descriptor, signature, defaultValue);
FieldDescription.InDefinedShape fieldDescription = fieldsByName.get(internalName);
for (Entry entry : entries) {
if (entry.matches(fieldDescription)) {
fieldVisitor = entry.wrap(instrumentedType, fieldDescription, fieldVisitor);
}
}
return fieldVisitor;
}
/**
* Returns the outer instance.
*
* @return The outer instance.
*/
private ForDeclaredFields getOuter() {
return ForDeclaredFields.this;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
DispatchingVisitor that = ((DispatchingVisitor) other);
return instrumentedType.equals(that.instrumentedType)
&& cv.equals(that.cv)
&& getOuter().equals(that.getOuter());
}
@Override
public int hashCode() {
int result = getOuter().hashCode();
result = 31 * result + instrumentedType.hashCode();
result = 31 * result + cv.hashCode();
return result;
}
@Override
public String toString() {
return "AsmVisitorWrapper.ForDeclaredFields.DispatchingVisitor{" +
"outer=" + getOuter() +
", instrumentedType=" + instrumentedType +
", fieldsByName=" + fieldsByName +
'}';
}
}
}
/**
*
* An ASM visitor wrapper that allows to wrap declared methods of the instrumented type with a {@link MethodVisitorWrapper}.
*
*
* Note: Inherited methods are not matched by this visitor, even if they are intercepted by a normal interception.
*
*/
class ForDeclaredMethods extends AbstractBase {
/**
* The list of entries that describe matched methods in their application order.
*/
private final List entries;
/**
* Creates a new visitor wrapper for declared methods.
*/
public ForDeclaredMethods() {
this(Collections.emptyList());
}
/**
* Creates a new visitor wrapper for declared methods.
*
* @param entries The list of entries that describe matched methods in their application order.
*/
protected ForDeclaredMethods(List entries) {
this.entries = entries;
}
/**
* Defines a new method visitor wrapper to be applied if the given method matcher is matched. Previously defined
* entries are applied before the given matcher is applied.
*
* @param matcher The matcher to identify methods to be wrapped.
* @param methodVisitorWrapper The method visitor wrapper to be applied if the given matcher is matched.
* @return A new ASM visitor wrapper that applied the given method visitor wrapper if the supplied matcher is matched.
*/
public ForDeclaredMethods method(ElementMatcher super MethodDescription.InDefinedShape> matcher, MethodVisitorWrapper methodVisitorWrapper) {
return new ForDeclaredMethods(CompoundList.of(entries, new Entry(matcher, methodVisitorWrapper)));
}
@Override
public ClassVisitor wrap(TypeDescription instrumentedType, ClassVisitor classVisitor) {
return new DispatchingVisitor(classVisitor, instrumentedType);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& entries.equals(((ForDeclaredMethods) other).entries);
}
@Override
public int hashCode() {
return entries.hashCode();
}
@Override
public String toString() {
return "AsmVisitorWrapper.ForDeclaredMethods{" +
"entries=" + entries +
'}';
}
/**
* A method visitor wrapper that allows for wrapping a {@link MethodVisitor} defining a declared method.
*/
public interface MethodVisitorWrapper {
/**
* Wraps a method visitor.
*
* @param instrumentedType The instrumented type.
* @param methodDescription The method that is currently being defined.
* @param methodVisitor The original field visitor that defines the given method.
* @return The wrapped method visitor.
*/
MethodVisitor wrap(TypeDescription instrumentedType, MethodDescription.InDefinedShape methodDescription, MethodVisitor methodVisitor);
}
/**
* An entry describing a method visitor wrapper paired with a matcher for fields to be wrapped.
*/
protected static class Entry implements ElementMatcher, MethodVisitorWrapper {
/**
* The matcher to identify methods to be wrapped.
*/
private final ElementMatcher super MethodDescription.InDefinedShape> matcher;
/**
* The method visitor wrapper to be applied if the given matcher is matched.
*/
private final MethodVisitorWrapper methodVisitorWrapper;
/**
* Creates a new entry.
*
* @param matcher The matcher to identify methods to be wrapped.
* @param methodVisitorWrapper The method visitor wrapper to be applied if the given matcher is matched.
*/
protected Entry(ElementMatcher super MethodDescription.InDefinedShape> matcher, MethodVisitorWrapper methodVisitorWrapper) {
this.matcher = matcher;
this.methodVisitorWrapper = methodVisitorWrapper;
}
@Override
public boolean matches(MethodDescription.InDefinedShape target) {
return target != null && matcher.matches(target);
}
@Override
public MethodVisitor wrap(TypeDescription instrumentedType, MethodDescription.InDefinedShape methodDescription, MethodVisitor methodVisitor) {
return methodVisitorWrapper.wrap(instrumentedType, methodDescription, methodVisitor);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Entry entry = (Entry) other;
return matcher.equals(entry.matcher)
&& methodVisitorWrapper.equals(entry.methodVisitorWrapper);
}
@Override
public int hashCode() {
int result = matcher.hashCode();
result = 31 * result + methodVisitorWrapper.hashCode();
return result;
}
@Override
public String toString() {
return "AsmVisitorWrapper.ForDeclaredMethods.Entry{" +
"matcher=" + matcher +
", methodVisitorWrapper=" + methodVisitorWrapper +
'}';
}
}
/**
* A class visitor that applies the outer ASM visitor for identifying declared methods.
*/
protected class DispatchingVisitor extends ClassVisitor {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* A mapping of fields by their name.
*/
private final Map methodsByName;
/**
* Creates a new dispatching visitor.
*
* @param classVisitor The underlying class visitor.
* @param instrumentedType The instrumented type.
*/
protected DispatchingVisitor(ClassVisitor classVisitor, TypeDescription instrumentedType) {
super(Opcodes.ASM5, classVisitor);
this.instrumentedType = instrumentedType;
methodsByName = new HashMap();
for (MethodDescription.InDefinedShape methodDescription : instrumentedType.getDeclaredMethods()) {
methodsByName.put(methodDescription.getInternalName() + methodDescription.getDescriptor(), methodDescription);
}
}
@Override
public MethodVisitor visitMethod(int modifiers, String internalName, String descriptor, String signature, String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(modifiers, internalName, descriptor, signature, exceptions);
MethodDescription.InDefinedShape methodDescription = methodsByName.get(internalName + descriptor);
for (Entry entry : entries) {
if (entry.matches(methodDescription)) {
methodVisitor = entry.wrap(instrumentedType, methodDescription, methodVisitor);
}
}
return methodVisitor;
}
/**
* Returns the outer instance.
*
* @return The outer instance.
*/
private ForDeclaredMethods getOuter() {
return ForDeclaredMethods.this;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
DispatchingVisitor that = ((DispatchingVisitor) other);
return instrumentedType.equals(that.instrumentedType)
&& getOuter().equals(that.getOuter())
&& cv.equals(that.cv);
}
@Override
public int hashCode() {
int result = getOuter().hashCode();
result = 31 * result + instrumentedType.hashCode();
result = 31 * result + cv.hashCode();
return result;
}
@Override
public String toString() {
return "AsmVisitorWrapper.ForDeclaredMethods.DispatchingVisitor{" +
"outer=" + getOuter() +
", instrumentedType=" + instrumentedType +
", methodsByName=" + methodsByName +
'}';
}
}
}
/**
* An ordered, immutable chain of {@link AsmVisitorWrapper}s.
*/
class Compound implements AsmVisitorWrapper {
/**
* The class visitor wrappers that are represented by this chain in their order. This list must not be mutated.
*/
private final List extends AsmVisitorWrapper> asmVisitorWrappers;
/**
* Creates a new immutable chain based on an existing list of {@link AsmVisitorWrapper}s
* where no copy of the received array is made.
*
* @param asmVisitorWrapper An array of {@link AsmVisitorWrapper}s where elements
* at the beginning of the list are applied first, i.e. will be at the bottom of the generated
* {@link org.testifyproject.bytebuddy.jar.asm.ClassVisitor}.
*/
public Compound(AsmVisitorWrapper... asmVisitorWrapper) {
this(Arrays.asList(asmVisitorWrapper));
}
/**
* Creates a new immutable chain based on an existing list of {@link AsmVisitorWrapper}s
* where no copy of the received list is made.
*
* @param asmVisitorWrappers A list of {@link AsmVisitorWrapper}s where elements
* at the beginning of the list are applied first, i.e. will be at the bottom of the generated
* {@link org.testifyproject.bytebuddy.jar.asm.ClassVisitor}.
*/
public Compound(List extends AsmVisitorWrapper> asmVisitorWrappers) {
this.asmVisitorWrappers = asmVisitorWrappers;
}
@Override
public int mergeWriter(int flags) {
for (AsmVisitorWrapper asmVisitorWrapper : asmVisitorWrappers) {
flags = asmVisitorWrapper.mergeWriter(flags);
}
return flags;
}
@Override
public int mergeReader(int flags) {
for (AsmVisitorWrapper asmVisitorWrapper : asmVisitorWrappers) {
flags = asmVisitorWrapper.mergeReader(flags);
}
return flags;
}
@Override
public ClassVisitor wrap(TypeDescription instrumentedType, ClassVisitor classVisitor) {
for (AsmVisitorWrapper asmVisitorWrapper : asmVisitorWrappers) {
classVisitor = asmVisitorWrapper.wrap(instrumentedType, classVisitor);
}
return classVisitor;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& asmVisitorWrappers.equals(((Compound) other).asmVisitorWrappers);
}
@Override
public int hashCode() {
return asmVisitorWrappers.hashCode();
}
@Override
public String toString() {
return "AsmVisitorWrapper.Compound{asmVisitorWrappers=" + asmVisitorWrappers + '}';
}
}
}