com.ui4j.bytebuddy.instrumentation.MethodCall Maven / Gradle / Ivy
The newest version!
package com.ui4j.bytebuddy.instrumentation;
import com.ui4j.bytebuddy.instrumentation.field.FieldDescription;
import com.ui4j.bytebuddy.instrumentation.field.FieldList;
import com.ui4j.bytebuddy.instrumentation.method.MethodDescription;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.ByteCodeAppender;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.Duplication;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.Removal;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.StackManipulation;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.TypeCreation;
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.constant.*;
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.utility.RandomString;
import com.ui4j.bytebuddy.jar.asm.MethodVisitor;
import com.ui4j.bytebuddy.jar.asm.Opcodes;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.ui4j.bytebuddy.matcher.ElementMatchers.named;
import static com.ui4j.bytebuddy.utility.ByteBuddyCommons.*;
/**
* This {@link com.ui4j.bytebuddy.instrumentation.Instrumentation} allows the invocation of a specified method while
* providing explicit arguments to this method.
*/
public class MethodCall implements Instrumentation {
/**
* The method locator to use.
*/
protected final MethodLocator methodLocator;
/**
* The target handler to use.
*/
protected final TargetHandler targetHandler;
/**
* The argument loader to load arguments onto the operand stack in their application order.
*/
protected final List argumentLoaders;
/**
* The method invoker to use.
*/
protected final MethodInvoker methodInvoker;
/**
* The termination handler to use.
*/
protected final TerminationHandler terminationHandler;
/**
* The assigner to use.
*/
protected final Assigner assigner;
/**
* {@code true} if a return value of the called method should be attempted to be type-casted to the return
* type of the instrumented method.
*/
protected final boolean dynamicallyTyped;
/**
* Creates a new method call instrumentation.
*
* @param methodLocator The method locator to use.
* @param targetHandler The target handler to use.
* @param argumentLoaders The argument loader to load arguments onto the operand stack in
* their application order.
* @param methodInvoker The method invoker to use.
* @param terminationHandler The termination handler to use.
* @param assigner The assigner to use.
* @param dynamicallyTyped {@code true} if a return value of the called method should be attempted
* to be type-casted to the return type of the instrumented method.
*/
protected MethodCall(MethodLocator methodLocator,
TargetHandler targetHandler,
List argumentLoaders,
MethodInvoker methodInvoker,
TerminationHandler terminationHandler,
Assigner assigner,
boolean dynamicallyTyped) {
this.methodLocator = methodLocator;
this.targetHandler = targetHandler;
this.argumentLoaders = argumentLoaders;
this.methodInvoker = methodInvoker;
this.terminationHandler = terminationHandler;
this.assigner = assigner;
this.dynamicallyTyped = dynamicallyTyped;
}
/**
* Returns the default assigner to be used by this instrumentation.
*
* @return The default assigner to be used by this instrumentation.
*/
private static Assigner defaultAssigner() {
return new VoidAwareAssigner(new PrimitiveTypeAwareAssigner(ReferenceTypeAwareAssigner.INSTANCE));
}
/**
* Returns the default value for using dynamically typed value assignments.
*
* @return The default value for using dynamically typed value assignments.
*/
private static boolean defaultDynamicallyTyped() {
return false;
}
/**
* Invokes the given method. Without further specification, the method is invoked without any arguments on
* the instance of the instrument class or statically, if the given method is {@code static}.
*
* @param method The method to invoke.
* @return A method call instrumentation that invokes the given method without providing any arguments.
*/
public static WithoutSpecifiedTarget invoke(Method method) {
return invoke(new MethodDescription.ForLoadedMethod(nonNull(method)));
}
/**
* Invokes the given constructor on the instance of the instrumented type.
*
* @param constructor The constructor to invoke.
* @return A method call instrumentation that invokes the given constructor without providing any arguments.
*/
public static WithoutSpecifiedTarget invoke(Constructor> constructor) {
return invoke(new MethodDescription.ForLoadedConstructor(nonNull(constructor)));
}
/**
* Invokes the given method. If the method description describes a constructor, it is automatically invoked as
* a special method invocation on the instance of the instrumented type. The same is true for {@code private}
* methods. Finally, {@code static} methods are invoked statically.
*
* @param methodDescription The method to invoke.
* @return A method call instrumentation that invokes the given method without providing any arguments.
*/
public static WithoutSpecifiedTarget invoke(MethodDescription methodDescription) {
return invoke(new MethodLocator.ForExplicitMethod(nonNull(methodDescription)));
}
/**
* Invokes a method using the provided method locator.
*
* @param methodLocator The method locator to apply for locating the method to invoke given the instrumented
* method.
* @return A method call instrumentation that uses the provided method locator for resolving the method
* to be invoked.
*/
public static WithoutSpecifiedTarget invoke(MethodLocator methodLocator) {
return new WithoutSpecifiedTarget(nonNull(methodLocator));
}
/**
* Invokes the given constructor in order to create an instance.
*
* @param constructor The constructor to invoke.
* @return A method call that invokes the given constructor without providing any arguments.
*/
public static MethodCall construct(Constructor> constructor) {
return construct(new MethodDescription.ForLoadedConstructor(nonNull(constructor)));
}
/**
* Invokes the given constructor in order to create an instance.
*
* @param methodDescription A description of the constructor to invoke.
* @return A method call that invokes the given constructor without providing any arguments.
*/
public static MethodCall construct(MethodDescription methodDescription) {
if (!methodDescription.isConstructor()) {
throw new IllegalArgumentException("Not a constructor: " + methodDescription);
}
return new MethodCall(new MethodLocator.ForExplicitMethod(methodDescription),
TargetHandler.ForConstructingInvocation.INSTANCE,
Collections.emptyList(),
MethodInvoker.ForStandardInvocation.INSTANCE,
TerminationHandler.ForMethodReturn.INSTANCE,
defaultAssigner(),
defaultDynamicallyTyped());
}
/**
* Invokes the method that is instrumented by the returned instance by a super method invocation.
*
* @return A method call that invokes the method being instrumented.
*/
public static MethodCall invokeSuper() {
return new WithoutSpecifiedTarget(MethodLocator.ForInterceptedMethod.INSTANCE).onSuper();
}
/**
* Defines a number of arguments to be handed to the method that is being invoked by this instrumentation. Any
* wrapper type instances for primitive values, instances of {@link java.lang.String} or {@code null} are loaded
* directly onto the operand stack. This might corrupt referential identity for these values. Any other values
* are stored within a {@code static} field that is added to the instrumented type.
*
* @param argument The arguments to provide to the method that is being called in their order.
* @return A method call that hands the provided arguments to the invoked method.
*/
public MethodCall with(Object... argument) {
List argumentLoaders = new ArrayList(argument.length);
for (Object anArgument : argument) {
argumentLoaders.add(ArgumentLoader.ForStaticField.of(anArgument));
}
return new MethodCall(methodLocator,
targetHandler,
join(this.argumentLoaders, argumentLoaders),
methodInvoker,
terminationHandler,
assigner,
dynamicallyTyped);
}
/**
* Defines a number of arguments to be handed to the method that is being invoked by this instrumentation. Any
* value is stored within a field in order to preserve referential identity. As an exception, the {@code null}
* value is not stored within a field.
*
* @param argument The arguments to provide to the method that is being called in their order.
* @return A method call that hands the provided arguments to the invoked method.
*/
public MethodCall withReference(Object... argument) {
List argumentLoaders = new ArrayList(argument.length);
for (Object anArgument : argument) {
argumentLoaders.add(anArgument == null
? ArgumentLoader.ForNullConstant.INSTANCE
: new ArgumentLoader.ForStaticField(anArgument));
}
return new MethodCall(methodLocator,
targetHandler,
join(this.argumentLoaders, argumentLoaders),
methodInvoker,
terminationHandler,
assigner,
dynamicallyTyped);
}
/**
* Defines a number of arguments of the instrumented method by their parameter indices to be handed
* to the invoked method as an argument.
*
* @param index The parameter indices of the instrumented method to be handed to the invoked method as an
* argument in their order. The indices are zero-based.
* @return A method call that hands the provided arguments to the invoked method.
*/
public MethodCall withArgument(int... index) {
List argumentLoaders = new ArrayList(index.length);
for (int anIndex : index) {
if (anIndex < 0) {
throw new IllegalArgumentException("Negative index: " + anIndex);
}
argumentLoaders.add(new ArgumentLoader.ForMethodParameter(anIndex));
}
return new MethodCall(methodLocator,
targetHandler,
join(this.argumentLoaders, argumentLoaders),
methodInvoker,
terminationHandler,
assigner,
dynamicallyTyped);
}
/**
* Assigns the {@code this} reference to the next parameter.
*
* @return This method call where the next parameter is a assigned a reference to the {@code this} reference
* of the instance of the intercepted method.
*/
public MethodCall withThis() {
return new MethodCall(methodLocator,
targetHandler,
join(argumentLoaders, ArgumentLoader.ForThisReference.INSTANCE),
methodInvoker,
terminationHandler,
assigner,
dynamicallyTyped);
}
/**
* Defines a method call which fetches a value from an instance field. The value of the field needs to be
* defined manually and is initialized with {@code null}. The field itself is defined by this instrumentation.
*
* @param type The type of the field.
* @param name The name of the field.
* @return A method call which assigns the next parameter to the value of the instance field.
*/
public MethodCall withInstanceField(Class> type, String name) {
return withInstanceField(new TypeDescription.ForLoadedType(nonNull(type)), name);
}
/**
* Defines a method call which fetches a value from an instance field. The value of the field needs to be
* defined manually and is initialized with {@code null}. The field itself is defined by this instrumentation.
*
* @param typeDescription The type of the field.
* @param name The name of the field.
* @return A method call which assigns the next parameter to the value of the instance field.
*/
public MethodCall withInstanceField(TypeDescription typeDescription, String name) {
return new MethodCall(methodLocator,
targetHandler,
join(argumentLoaders, new ArgumentLoader.ForInstanceField(nonNull(typeDescription), nonNull(name))),
methodInvoker,
terminationHandler,
assigner,
dynamicallyTyped);
}
/**
* Defines a method call which fetches a value from an existing field. The field is not defines by this
* instrumentation.
*
* @param fieldName The name of the field.
* @return A method call which assigns the next parameter to the value of the given field.
*/
public MethodCall withField(String... fieldName) {
List argumentLoaders = new ArrayList(fieldName.length);
for (String aFieldName : fieldName) {
argumentLoaders.add(new ArgumentLoader.ForExistingField(aFieldName));
}
return new MethodCall(methodLocator,
targetHandler,
join(this.argumentLoaders, argumentLoaders),
methodInvoker,
terminationHandler,
assigner,
dynamicallyTyped);
}
/**
* Defines an assigner to be used for assigning values to the parameters of the invoked method. This assigner
* is also used for assigning the invoked method's return value to the return type of the instrumented method,
* if this method is not chained with
* {@link com.ui4j.bytebuddy.instrumentation.MethodCall#andThen(com.ui4j.bytebuddy.instrumentation.Instrumentation)} such
* that a return value of this method call is discarded.
*
* @param assigner The assigner to use.
* @param dynamicallyTyped {@code true} if the return value assignment should attempt a type casting when
* assigning incompatible values.
* @return This method call using the provided assigner.
*/
public MethodCall withAssigner(Assigner assigner, boolean dynamicallyTyped) {
return new MethodCall(methodLocator,
targetHandler,
argumentLoaders,
methodInvoker,
terminationHandler,
nonNull(assigner),
dynamicallyTyped);
}
/**
* Applies another instrumentation after invoking this method call. A return value that is the result of this
* method call is dropped.
*
* @param instrumentation The instrumentation that is to be applied after applying this
* method call instrumentation.
* @return An instrumentation that first applies this method call and the given instrumentation right after.
*/
public Instrumentation andThen(Instrumentation instrumentation) {
return new Instrumentation.Compound(new MethodCall(methodLocator,
targetHandler,
argumentLoaders,
methodInvoker,
TerminationHandler.ForChainedInvocation.INSTANCE,
assigner,
dynamicallyTyped), nonNull(instrumentation));
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
for (ArgumentLoader argumentLoader : argumentLoaders) {
instrumentedType = argumentLoader.prepare(instrumentedType);
}
return targetHandler.prepare(instrumentedType);
}
@Override
public ByteCodeAppender appender(Target instrumentationTarget) {
return new Appender(instrumentationTarget);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof MethodCall)) return false;
MethodCall that = (MethodCall) other;
return dynamicallyTyped == that.dynamicallyTyped
&& argumentLoaders.equals(that.argumentLoaders)
&& assigner.equals(that.assigner)
&& methodInvoker.equals(that.methodInvoker)
&& methodLocator.equals(that.methodLocator)
&& targetHandler.equals(that.targetHandler)
&& terminationHandler.equals(that.terminationHandler);
}
@Override
public int hashCode() {
int result = methodLocator.hashCode();
result = 31 * result + targetHandler.hashCode();
result = 31 * result + argumentLoaders.hashCode();
result = 31 * result + methodInvoker.hashCode();
result = 31 * result + terminationHandler.hashCode();
result = 31 * result + assigner.hashCode();
result = 31 * result + (dynamicallyTyped ? 1 : 0);
return result;
}
@Override
public String toString() {
return "MethodCall{" +
"methodLocator=" + methodLocator +
", targetHandler=" + targetHandler +
", argumentLoaders=" + argumentLoaders +
", methodInvoker=" + methodInvoker +
", terminationHandler=" + terminationHandler +
", assigner=" + assigner +
", dynamicallyTyped=" + dynamicallyTyped +
'}';
}
/**
* A method locator is responsible for identifying the method that is to be invoked
* by a {@link com.ui4j.bytebuddy.instrumentation.MethodCall}.
*/
public static interface MethodLocator {
/**
* Resolves the method to be invoked.
*
* @param instrumentedMethod The method being instrumented.
* @return The method to invoke.
*/
MethodDescription resolve(MethodDescription instrumentedMethod);
/**
* A method locator that simply returns the intercepted method.
*/
static enum ForInterceptedMethod implements MethodLocator {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public MethodDescription resolve(MethodDescription instrumentedMethod) {
return instrumentedMethod;
}
}
/**
* Invokes a given method.
*/
static class ForExplicitMethod implements MethodLocator {
/**
* The method to be invoked.
*/
private final MethodDescription methodDescription;
/**
* Creates a new method locator for a given method.
*
* @param methodDescription The method to be invoked.
*/
public ForExplicitMethod(MethodDescription methodDescription) {
this.methodDescription = methodDescription;
}
@Override
public MethodDescription resolve(MethodDescription instrumentedMethod) {
return methodDescription;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& methodDescription.equals(((ForExplicitMethod) other).methodDescription);
}
@Override
public int hashCode() {
return methodDescription.hashCode();
}
@Override
public String toString() {
return "MethodCall.MethodLocator.ForExplicitMethod{" +
"methodDescription=" + methodDescription +
'}';
}
}
}
/**
* A target handler is responsible for invoking a method for a
* {@link com.ui4j.bytebuddy.instrumentation.MethodCall}.
*/
protected static interface TargetHandler {
/**
* Creates a stack manipulation that represents the method's invocation.
*
* @param methodDescription The method to be invoked.
* @param instrumentedType The instrumented type.
* @return A stack manipulation that invokes the method.
*/
StackManipulation resolve(MethodDescription methodDescription, TypeDescription instrumentedType);
/**
* Prepares the instrumented type in order to allow for the represented invocation.
*
* @param instrumentedType The instrumented type.
* @return The prepared instrumented type.
*/
InstrumentedType prepare(InstrumentedType instrumentedType);
/**
* A target handler that invokes a method either on the instance of the instrumented
* type or as a static method.
*/
static enum ForSelfOrStaticInvocation implements TargetHandler {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public StackManipulation resolve(MethodDescription methodDescription, TypeDescription instrumentedType) {
return new StackManipulation.Compound(
methodDescription.isStatic()
? StackManipulation.LegalTrivial.INSTANCE
: MethodVariableAccess.REFERENCE.loadOffset(0),
methodDescription.isConstructor()
? Duplication.SINGLE
: StackManipulation.LegalTrivial.INSTANCE
);
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
}
/**
* Invokes a method in order to construct a new instance.
*/
static enum ForConstructingInvocation implements TargetHandler {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public StackManipulation resolve(MethodDescription methodDescription, TypeDescription instrumentedType) {
return new StackManipulation.Compound(TypeCreation.forType(methodDescription.getDeclaringType()), Duplication.SINGLE);
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
}
/**
* A target handler that invokes a method on an instance that is stored in a static field.
*/
static class ForStaticField implements TargetHandler {
/**
* The name prefix of the field to store the instance.
*/
private static final String FIELD_PREFIX = "invocationTarget";
/**
* The modifiers of the static field to store the instance.
*/
private static final int FIELD_MODIFIERS = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC;
/**
* The target on which the method is to be invoked.
*/
private final Object target;
/**
* The name of the field to store the target.
*/
private final String fieldName;
/**
* Creares a new target handler for a static field.
*
* @param target The target on which the method is to be invoked.
*/
public ForStaticField(Object target) {
this.target = target;
fieldName = String.format("%s$%s", FIELD_PREFIX, RandomString.make());
}
@Override
public StackManipulation resolve(MethodDescription methodDescription, TypeDescription instrumentedType) {
if (methodDescription.isStatic() || !methodDescription.getDeclaringType().isInstance(target)) {
throw new IllegalStateException("Cannot invoke " + methodDescription + " on " + target);
}
return FieldAccess.forField(instrumentedType.getDeclaredFields()
.filter(named(fieldName)).getOnly()).getter();
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType
.withField(fieldName, new TypeDescription.ForLoadedType(target.getClass()), FIELD_MODIFIERS)
.withInitializer(LoadedTypeInitializer.ForStaticField.nonAccessible(fieldName, target));
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& target.equals(((ForStaticField) other).target);
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return "MethodCall.TargetHandler.ForStaticField{" +
"target=" + target +
", fieldName='" + fieldName + '\'' +
'}';
}
}
/**
* Creates a target handler that stores the instance to invoke a method on in an instance field.
*/
static class ForInstanceField implements TargetHandler {
/**
* The modifiers of the field.
*/
private static final int FIELD_MODIFIERS = Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC;
/**
* The name of the field.
*/
private final String fieldName;
/**
* The type of the field.
*/
private final TypeDescription fieldType;
/**
* Creates a new target handler for storing a method invocation target in an
* instance field.
*
* @param fieldName The name of the field.
* @param fieldType The type of the field.
*/
public ForInstanceField(String fieldName, TypeDescription fieldType) {
this.fieldName = fieldName;
this.fieldType = fieldType;
}
@Override
public StackManipulation resolve(MethodDescription methodDescription, TypeDescription instrumentedType) {
if (methodDescription.isStatic() || !methodDescription.isInvokableOn(fieldType)) {
throw new IllegalStateException("Cannot invoke " + methodDescription + " on " + fieldType);
}
return new StackManipulation.Compound(
methodDescription.isStatic()
? StackManipulation.LegalTrivial.INSTANCE
: MethodVariableAccess.REFERENCE.loadOffset(0),
FieldAccess.forField(instrumentedType.getDeclaredFields().filter(named(fieldName)).getOnly()).getter());
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType.withField(fieldName, fieldType, FIELD_MODIFIERS);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass()) && fieldName
.equals(((ForInstanceField) other).fieldName) && fieldType
.equals(((ForInstanceField) other).fieldType);
}
@Override
public int hashCode() {
int result = fieldName.hashCode();
result = 31 * result + fieldType.hashCode();
return result;
}
@Override
public String toString() {
return "MethodCall.TargetHandler.ForInstanceField{" +
"fieldName='" + fieldName + '\'' +
", fieldType=" + fieldType +
'}';
}
}
}
/**
* An argument loader is responsible for loading an argument for an invoked method
* onto the operand stack.
*/
protected static interface ArgumentLoader {
/**
* Loads the argument that is represented by this instance onto the operand stack.
*
* @param instrumentedType The instrumented type.
* @param interceptedMethod The method being intercepted.
* @param targetType The target type.
* @param assigner The assigner to be used.
* @param dynamicallyTyped {@code true} if an assignment should be consider type castings.
* @return The stack manipulation that loads the represented argument onto the stack.
*/
StackManipulation resolve(TypeDescription instrumentedType,
MethodDescription interceptedMethod,
TypeDescription targetType,
Assigner assigner,
boolean dynamicallyTyped);
/**
* Prepares the instrumented type in order to allow the loading of the represented argument.
*
* @param instrumentedType The instrumented type.
* @return The prepared instrumented type.
*/
InstrumentedType prepare(InstrumentedType instrumentedType);
/**
* An argument loader that loads the {@code null} value onto the operand stack.
*/
static enum ForNullConstant implements ArgumentLoader {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public StackManipulation resolve(TypeDescription instrumentedType,
MethodDescription interceptedMethod,
TypeDescription targetType,
Assigner assigner,
boolean dynamicallyTyped) {
if (targetType.isPrimitive()) {
throw new IllegalStateException("Cannot assign null to " + targetType);
}
return NullConstant.INSTANCE;
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
}
/**
* An argument loader that assigns the {@code this} reference to a parameter.
*/
static enum ForThisReference implements ArgumentLoader {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public StackManipulation resolve(TypeDescription instrumentedType,
MethodDescription interceptedMethod,
TypeDescription targetType,
Assigner assigner,
boolean dynamicallyTyped) {
if (interceptedMethod.isStatic()) {
throw new IllegalStateException(interceptedMethod + " has no instance");
}
StackManipulation stackManipulation = new StackManipulation.Compound(
MethodVariableAccess.REFERENCE.loadOffset(0),
assigner.assign(instrumentedType, targetType, dynamicallyTyped));
if (!stackManipulation.isValid()) {
throw new IllegalStateException("Cannot assign " + instrumentedType + " to " + targetType);
}
return stackManipulation;
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
}
/**
* Loads a parameter of the instrumented method onto the operand stack.
*/
static class ForMethodParameter implements ArgumentLoader {
/**
* The index of the parameter to be loaded onto the operand stack.
*/
private final int index;
/**
* Creates an argument loader for a parameter of the instrumented method.
*
* @param index The index of the parameter to be loaded onto the operand stack.
*/
public ForMethodParameter(int index) {
this.index = index;
}
@Override
public StackManipulation resolve(TypeDescription instrumentedType,
MethodDescription interceptedMethod,
TypeDescription targetType,
Assigner assigner,
boolean dynamicallyTyped) {
if (index >= interceptedMethod.getParameters().size()) {
throw new IllegalStateException(interceptedMethod + " does not have a parameter with index " + index);
}
TypeDescription originType = interceptedMethod.getParameters().get(index).getTypeDescription();
StackManipulation stackManipulation = new StackManipulation.Compound(
MethodVariableAccess.forType(originType).loadOffset(interceptedMethod.getParameters().get(index).getOffset()),
assigner.assign(originType, targetType, dynamicallyTyped));
if (!stackManipulation.isValid()) {
throw new IllegalStateException("Cannot assign " + originType + " to " + targetType
+ " for " + interceptedMethod);
}
return stackManipulation;
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& index == ((ForMethodParameter) other).index;
}
@Override
public int hashCode() {
return index;
}
@Override
public String toString() {
return "MethodCall.ArgumentLoader.ForMethodParameter{" +
"index=" + index +
'}';
}
}
/**
* Loads a value onto the operand stack that is stored in a static field.
*/
static class ForStaticField implements ArgumentLoader {
/**
* The name prefix of the field to store the argument.
*/
private static final String FIELD_PREFIX = "methodCall";
/**
* The modifier of the field.
*/
private static final int FIELD_MODIFIER = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC;
/**
* The value to be stored in the field.
*/
private final Object value;
/**
* The name of the field.
*/
private final String fieldName;
/**
* Creates a new argument loader that stores the value in a field.
*
* @param value The value to be stored and loaded onto the operand stack.
*/
protected ForStaticField(Object value) {
this.value = value;
fieldName = String.format("%s$%s", FIELD_PREFIX, RandomString.make());
}
/**
* Resolves a value to be stored to be stored in the constant pool of the class, if possible.
*
* @param value The value to be stored in the field.
* @return An argument loader that loads the given value onto the operand stack.
*/
public static ArgumentLoader of(Object value) {
if (value == null) {
return ForNullConstant.INSTANCE;
} else if (value.getClass() == String.class) {
return new ForTextConstant((String) value);
} else if (value.getClass() == Boolean.class) {
return new ForBooleanConstant((Boolean) value);
} else if (value.getClass() == Byte.class) {
return new ForByteConstant((Byte) value);
} else if (value.getClass() == Short.class) {
return new ForShortConstant((Short) value);
} else if (value.getClass() == Character.class) {
return new ForCharacterConstant((Character) value);
} else if (value.getClass() == Integer.class) {
return new ForIntegerConstant((Integer) value);
} else if (value.getClass() == Long.class) {
return new ForLongConstant((Long) value);
} else if (value.getClass() == Float.class) {
return new ForFloatConstant((Float) value);
} else if (value.getClass() == Double.class) {
return new ForDoubleConstant((Double) value);
} else {
return new ForStaticField(value);
}
}
@Override
public StackManipulation resolve(TypeDescription instrumentedType,
MethodDescription interceptedMethod,
TypeDescription targetType,
Assigner assigner,
boolean dynamicallyTyped) {
StackManipulation stackManipulation = new StackManipulation.Compound(
FieldAccess.forField(instrumentedType.getDeclaredFields().filter(named(fieldName)).getOnly()).getter(),
assigner.assign(new TypeDescription.ForLoadedType(value.getClass()), targetType, dynamicallyTyped));
if (!stackManipulation.isValid()) {
throw new IllegalStateException("Cannot assign " + value.getClass() + " to " + targetType);
}
return stackManipulation;
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType.withField(fieldName,
new TypeDescription.ForLoadedType(value.getClass()),
FIELD_MODIFIER)
.withInitializer(new LoadedTypeInitializer.ForStaticField