com.ui4j.bytebuddy.instrumentation.InvocationHandlerAdapter Maven / Gradle / Ivy
package com.ui4j.bytebuddy.instrumentation;
import com.ui4j.bytebuddy.instrumentation.method.MethodDescription;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.ByteCodeAppender;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.StackManipulation;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.Assigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.primitive.PrimitiveTypeAwareAssigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.primitive.VoidAwareAssigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.reference.ReferenceTypeAwareAssigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.collection.ArrayFactory;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.constant.MethodConstant;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.FieldAccess;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.MethodInvocation;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.MethodReturn;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.MethodVariableAccess;
import com.ui4j.bytebuddy.instrumentation.type.InstrumentedType;
import com.ui4j.bytebuddy.instrumentation.type.TypeDescription;
import com.ui4j.bytebuddy.instrumentation.type.TypeList;
import com.ui4j.bytebuddy.jar.asm.MethodVisitor;
import com.ui4j.bytebuddy.jar.asm.Opcodes;
import java.lang.reflect.InvocationHandler;
import java.util.ArrayList;
import java.util.List;
import static com.ui4j.bytebuddy.matcher.ElementMatchers.named;
import static com.ui4j.bytebuddy.utility.ByteBuddyCommons.isValidIdentifier;
import static com.ui4j.bytebuddy.utility.ByteBuddyCommons.nonNull;
/**
* An adapter for adapting an {@link java.lang.reflect.InvocationHandler}. The adapter allows the invocation handler
* to also intercept method calls to non-interface methods.
*/
public abstract class InvocationHandlerAdapter implements Instrumentation {
/**
* The prefix for field that are created for storing the instrumented value.
*/
private static final String PREFIX = "invocationHandler";
/**
* The name of the field for storing an invocation handler.
*/
protected final String fieldName;
/**
* The assigner that is used for assigning the return invocation handler's return value to the
* intercepted method's return value.
*/
protected final Assigner assigner;
/**
* Determines if the {@link java.lang.reflect.Method} instances that are handed to the intercepted methods are
* cached in {@code static} fields.
*/
protected final boolean cacheMethods;
/**
* Creates a new invocation handler for a given field.
*
* @param fieldName The name of the field.
* @param cacheMethods Determines if the {@link java.lang.reflect.Method} instances that are handed to the
* intercepted methods are cached in {@code static} fields.
* @param assigner The assigner to apply when defining this instrumentation.
*/
protected InvocationHandlerAdapter(String fieldName, boolean cacheMethods, Assigner assigner) {
this.fieldName = fieldName;
this.cacheMethods = cacheMethods;
this.assigner = assigner;
}
/**
* Creates a default assigner to use.
*
* @return The default assigner.
*/
private static Assigner defaultAssigner() {
return new VoidAwareAssigner(new PrimitiveTypeAwareAssigner(ReferenceTypeAwareAssigner.INSTANCE));
}
/**
* Creates an instrumentation for any instance of an {@link java.lang.reflect.InvocationHandler} that delegates
* all method interceptions to the given instance which will be stored in a {@code static} field.
*
* @param invocationHandler The invocation handler to which all method calls are delegated.
* @return An instrumentation that delegates all method interceptions to the given invocation handler.
*/
public static InvocationHandlerAdapter of(InvocationHandler invocationHandler) {
return of(invocationHandler, String.format("%s$%d", PREFIX, Math.abs(invocationHandler.hashCode())));
}
/**
* Creates an instrumentation for any instance of an {@link java.lang.reflect.InvocationHandler} that delegates
* all method interceptions to the given instance which will be stored in a {@code static} field.
*
* @param invocationHandler The invocation handler to which all method calls are delegated.
* @param fieldName The name of the field.
* @return An instrumentation that delegates all method interceptions to the given invocation handler.
*/
public static InvocationHandlerAdapter of(InvocationHandler invocationHandler, String fieldName) {
return new ForStaticDelegation(isValidIdentifier(fieldName), false, defaultAssigner(), nonNull(invocationHandler));
}
/**
* Creates an instrumentation for any {@link java.lang.reflect.InvocationHandler} that delegates
* all method interceptions to a {@code public} instance field with the given name. This field has to be
* set before any invocations are intercepted. Otherwise, a {@link java.lang.NullPointerException} will be
* thrown.
*
* @param fieldName The name of the field.
* @return An instrumentation that delegates all method interceptions to an instance field of the given name.
*/
public static InvocationHandlerAdapter toInstanceField(String fieldName) {
return new ForInstanceDelegation(isValidIdentifier(fieldName), false, defaultAssigner());
}
/**
* Returns a list of stack manipulations that loads all arguments of an instrumented method.
*
* @param instrumentedMethod The method that is instrumented.
* @return A list of stack manipulation that loads all arguments of an instrumented method.
*/
private List argumentValuesOf(MethodDescription instrumentedMethod) {
TypeList parameterTypes = instrumentedMethod.getParameterTypes();
List instruction = new ArrayList(parameterTypes.size());
TypeDescription objectType = new TypeDescription.ForLoadedType(Object.class);
int currentIndex = 1;
for (TypeDescription parameterType : parameterTypes) {
instruction.add(new StackManipulation.Compound(
MethodVariableAccess.forType(parameterType).loadFromIndex(currentIndex),
assigner.assign(parameterType, objectType, false)));
currentIndex += parameterType.getStackSize().getSize();
}
return instruction;
}
/**
* By default, any {@link java.lang.reflect.Method} instance that is handed over to an
* {@link java.lang.reflect.InvocationHandler} is created on each invocation of the method.
* {@link java.lang.reflect.Method} look-ups are normally cached by its defining {@link java.lang.Class} what
* makes a repeated look-up of a method little expensive. However, because {@link java.lang.reflect.Method}
* instances are mutable by their {@link java.lang.reflect.AccessibleObject} contact, any looked-up instance
* needs to be copied by its defining {@link java.lang.Class} before exposing it. This can cause performance
* deficits when a method is for example called repeatedly in a loop. By enabling the method cache, this
* performance penalty can be avoided by caching a single {@link java.lang.reflect.Method} instance for
* any intercepted method as a {@code static} field in the instrumented type.
*
* @return A similar invocation handler adapter which caches any {@link java.lang.reflect.Method} instance
* in form of a {@code static} field.
*/
public abstract AssignerConfigurable withMethodCache();
/**
* Applies an instrumentation that delegates to a invocation handler.
*
* @param methodVisitor The method visitor for writing the byte code to.
* @param instrumentationContext The instrumentation context for the current instrumentation.
* @param instrumentedMethod The method that is instrumented.
* @param instrumentedType The type that is instrumented.
* @param preparingManipulation A stack manipulation that applies any preparation to the operand stack.
* @return The size of the applied assignment.
*/
protected ByteCodeAppender.Size apply(MethodVisitor methodVisitor,
Context instrumentationContext,
MethodDescription instrumentedMethod,
TypeDescription instrumentedType,
StackManipulation preparingManipulation) {
TypeDescription objectType = new TypeDescription.ForLoadedType(Object.class);
TypeDescription invocationHandlerType = new TypeDescription.ForLoadedType(InvocationHandler.class);
StackManipulation.Size stackSize = new StackManipulation.Compound(
preparingManipulation,
FieldAccess.forField(instrumentedType.getDeclaredFields()
.filter((named(fieldName))).getOnly()).getter(),
MethodVariableAccess.forType(objectType).loadFromIndex(0),
cacheMethods
? MethodConstant.forMethod(instrumentedMethod).cached()
: MethodConstant.forMethod(instrumentedMethod),
ArrayFactory.targeting(objectType).withValues(argumentValuesOf(instrumentedMethod)),
MethodInvocation.invoke(invocationHandlerType.getDeclaredMethods().getOnly()),
assigner.assign(objectType, instrumentedMethod.getReturnType(), true),
MethodReturn.returning(instrumentedMethod.getReturnType())
).apply(methodVisitor, instrumentationContext);
return new ByteCodeAppender.Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
InvocationHandlerAdapter that = (InvocationHandlerAdapter) other;
return cacheMethods == that.cacheMethods
&& assigner.equals(that.assigner)
&& fieldName.equals(that.fieldName);
}
@Override
public int hashCode() {
int result = fieldName.hashCode();
result = 31 * result + assigner.hashCode();
result = 31 * result + (cacheMethods ? 1 : 0);
return result;
}
/**
* Allows for the configuration of an {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.Assigner}
* of an {@link com.ui4j.bytebuddy.instrumentation.InvocationHandlerAdapter}.
*/
protected static interface AssignerConfigurable extends Instrumentation {
/**
* Configures an assigner to use with this invocation handler adapter.
*
* @param assigner The assigner to apply when defining this instrumentation.
* @return This instrumentation with the given {@code assigner} configured.
*/
Instrumentation withAssigner(Assigner assigner);
}
/**
* An implementation of an {@link com.ui4j.bytebuddy.instrumentation.InvocationHandlerAdapter} that delegates method
* invocations to an adapter that is stored in a static field.
*/
protected static class ForStaticDelegation extends InvocationHandlerAdapter implements AssignerConfigurable {
/**
* The invocation handler to which method interceptions are to be delegated.
*/
protected final InvocationHandler invocationHandler;
/**
* Creates a new invocation handler adapter for delegating invocations to an invocation handler that is stored
* in a static field.
*
* @param fieldName The name of the field.
* @param cacheMethods Determines if the {@link java.lang.reflect.Method} instances that are handed to the
* intercepted methods are cached in {@code static} fields.
* @param assigner The assigner to apply when defining this instrumentation.
* @param invocationHandler The invocation handler to which all method calls are delegated.
*/
protected ForStaticDelegation(String fieldName,
boolean cacheMethods,
Assigner assigner,
InvocationHandler invocationHandler) {
super(fieldName, cacheMethods, assigner);
this.invocationHandler = invocationHandler;
}
@Override
public AssignerConfigurable withMethodCache() {
return new ForStaticDelegation(fieldName, true, assigner, invocationHandler);
}
@Override
public Instrumentation withAssigner(Assigner assigner) {
return new ForStaticDelegation(fieldName, cacheMethods, nonNull(assigner), invocationHandler);
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType
.withField(fieldName, new TypeDescription.ForLoadedType(InvocationHandler.class), Opcodes.ACC_STATIC)
.withInitializer(LoadedTypeInitializer.ForStaticField.nonAccessible(fieldName, invocationHandler));
}
@Override
public ByteCodeAppender appender(Target instrumentationTarget) {
return new Appender(instrumentationTarget.getTypeDescription());
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& super.equals(other)
&& invocationHandler.equals(((ForStaticDelegation) other).invocationHandler);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + invocationHandler.hashCode();
}
@Override
public String toString() {
return "InvocationHandlerAdapter.ForStaticDelegation{" +
"fieldName=" + fieldName +
", cacheMethods=" + cacheMethods +
", invocationHandler=" + invocationHandler +
'}';
}
/**
* An appender for implementing the {@link com.ui4j.bytebuddy.instrumentation.InvocationHandlerAdapter.ForStaticDelegation}.
*/
protected class Appender implements ByteCodeAppender {
/**
* The instrumented type for which the methods are being intercepted.
*/
private final TypeDescription instrumentedType;
/**
* Creates a new appender.
*
* @param instrumentedType The type that is instrumented.
*/
protected Appender(TypeDescription instrumentedType) {
this.instrumentedType = instrumentedType;
}
@Override
public boolean appendsCode() {
return true;
}
@Override
public Size apply(MethodVisitor methodVisitor,
Context instrumentationContext,
MethodDescription instrumentedMethod) {
return ForStaticDelegation.this.apply(methodVisitor,
instrumentationContext,
instrumentedMethod,
instrumentedType,
StackManipulation.LegalTrivial.INSTANCE);
}
/**
* Returns the outer class.
*
* @return The outer class of this instance.
*/
private InvocationHandlerAdapter getInvocationHandlerAdapter() {
return ForStaticDelegation.this;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& instrumentedType.equals(((Appender) other).instrumentedType)
&& ForStaticDelegation.this.equals(((Appender) other).getInvocationHandlerAdapter());
}
@Override
public int hashCode() {
return 31 * ForStaticDelegation.this.hashCode() + instrumentedType.hashCode();
}
@Override
public String toString() {
return "InvocationHandlerAdapter.ForStaticDelegation.Appender{" +
"invocationHandlerAdapter=" + ForStaticDelegation.this +
"instrumentedType=" + instrumentedType +
'}';
}
}
}
/**
* An implementation of an {@link com.ui4j.bytebuddy.instrumentation.InvocationHandlerAdapter} that delegates method
* invocations to an adapter that is stored in an instance field.
*/
protected static class ForInstanceDelegation extends InvocationHandlerAdapter implements AssignerConfigurable {
/**
* Creates a new invocation handler adapter for delegating invocations to an invocation handler that is stored
* in an instance field.
*
* @param fieldName The name of the field.
* @param cacheMethods Determines if the {@link java.lang.reflect.Method} instances that are handed to the
* intercepted methods are cached in {@code static} fields.
* @param assigner The assigner to apply when defining this instrumentation.
*/
protected ForInstanceDelegation(String fieldName, boolean cacheMethods, Assigner assigner) {
super(fieldName, cacheMethods, assigner);
}
@Override
public AssignerConfigurable withMethodCache() {
return new ForInstanceDelegation(fieldName, true, assigner);
}
@Override
public Instrumentation withAssigner(Assigner assigner) {
return new ForInstanceDelegation(fieldName, cacheMethods, nonNull(assigner));
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType.withField(fieldName,
new TypeDescription.ForLoadedType(InvocationHandler.class),
Opcodes.ACC_PUBLIC);
}
@Override
public ByteCodeAppender appender(Target instrumentationTarget) {
return new Appender(instrumentationTarget.getTypeDescription());
}
@Override
public String toString() {
return "InvocationHandlerAdapter.ForInstanceDelegation{" +
"fieldName=" + fieldName +
"cacheMethods=" + cacheMethods +
'}';
}
/**
* An appender for implementing the {@link com.ui4j.bytebuddy.instrumentation.InvocationHandlerAdapter.ForInstanceDelegation}.
*/
protected class Appender implements ByteCodeAppender {
/**
* The type that is subject of the instrumentation.
*/
private final TypeDescription instrumentedType;
/**
* Creates a new appender.
*
* @param instrumentedType The type that is instrumented.
*/
protected Appender(TypeDescription instrumentedType) {
this.instrumentedType = instrumentedType;
}
@Override
public boolean appendsCode() {
return true;
}
@Override
public Size apply(MethodVisitor methodVisitor, Context instrumentationContext, MethodDescription instrumentedMethod) {
return ForInstanceDelegation.this.apply(methodVisitor,
instrumentationContext,
instrumentedMethod,
instrumentedType,
MethodVariableAccess.forType(instrumentedType).loadFromIndex(0));
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& instrumentedType.equals(((Appender) other).instrumentedType)
&& ForInstanceDelegation.this.equals(((Appender) other).getInvocationHandlerAdapter());
}
/**
* Returns the outer class.
*
* @return The outer class.
*/
private InvocationHandlerAdapter getInvocationHandlerAdapter() {
return ForInstanceDelegation.this;
}
@Override
public int hashCode() {
return 31 * ForInstanceDelegation.this.hashCode() + instrumentedType.hashCode();
}
@Override
public String toString() {
return "InvocationHandlerAdapter.ForInstanceDelegation.Appender{" +
"invocationHandlerAdapter=" + ForInstanceDelegation.this +
"instrumentedType=" + instrumentedType +
'}';
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy