com.ui4j.bytebuddy.instrumentation.Forwarding Maven / Gradle / Ivy
The newest version!
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.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.jar.asm.MethodVisitor;
import com.ui4j.bytebuddy.jar.asm.Opcodes;
import static com.ui4j.bytebuddy.matcher.ElementMatchers.named;
import static com.ui4j.bytebuddy.utility.ByteBuddyCommons.*;
/**
* This instrumentation forwards method invocations to another instance. For this, the intercepted method must be
* defined on a super type of the given delegation target. Static methods cannot be forwarded as they are not
* invoked on an instance.
*/
public class Forwarding implements Instrumentation {
/**
* The prefix of any implicit field name for storing a delegate..
*/
private static final String FIELD_PREFIX = "forwarding";
/**
* The name of the field.
*/
protected final String fieldName;
/**
* The type of the field.
*/
protected final TypeDescription fieldType;
/**
* A handler for preparing the instrumented type and the field invocation operation.
*/
protected final PreparationHandler preparationHandler;
/**
* Creates a new forwarding instrumentation.
*
* @param fieldName The name of the field.
* @param fieldType The type of the field.
* @param preparationHandler A handler for preparing the instrumented type and the field invocation operation.
*/
protected Forwarding(String fieldName, TypeDescription fieldType, PreparationHandler preparationHandler) {
this.fieldName = fieldName;
this.fieldType = fieldType;
this.preparationHandler = preparationHandler;
}
/**
* Forwards all intercepted method invocations to the given instance which is stored in a {@code static} field
* of the instrumented class. The field name is generated from the instance's hash code.
*
* @param delegate The delegate to which all intercepted methods should be forwarded.
* @return A corresponding instrumentation.
*/
public static Instrumentation to(Object delegate) {
return to(delegate, String.format("%s$%d", FIELD_PREFIX, delegate.hashCode()));
}
/**
* Forwards all intercepted method invocations to the given instance which is stored in a {@code static} field
* of the instrumented class.
*
* @param delegate The delegate to which all intercepted methods should be forwarded.
* @param fieldName The name of the field in which the delegate should be stored.
* @return A corresponding instrumentation.
*/
public static Instrumentation to(Object delegate, String fieldName) {
return new Forwarding(isValidIdentifier(fieldName),
new TypeDescription.ForLoadedType(nonNull(delegate).getClass()),
new PreparationHandler.ForStaticInstance(delegate));
}
/**
* Forwards all intercepted method invocations to a {@code static} field of the instrumented class. The value
* of this field must be set explicitly.
*
* @param fieldName The name of the field in which the delegate should be stored.
* @param fieldType The type of the field and thus the type of which the delegate is assumed to be of.
* @return A corresponding instrumentation.
*/
public static Instrumentation toStaticField(String fieldName, Class> fieldType) {
return toStaticField(fieldName, new TypeDescription.ForLoadedType(nonNull(fieldType)));
}
/**
* Forwards all intercepted method invocations to a {@code static} field of the instrumented class. The value
* of this field must be set explicitly.
*
* @param fieldName The name of the field in which the delegate should be stored.
* @param fieldType The type of the field and thus the type of which the delegate is assumed to be of.
* @return A corresponding instrumentation.
*/
public static Instrumentation toStaticField(String fieldName, TypeDescription fieldType) {
return new Forwarding(isValidIdentifier(fieldName),
nonVoid(fieldType),
PreparationHandler.ForStaticField.INSTANCE);
}
/**
* Forwards all intercepted method invocations to an instance field of the instrumented class. The value
* of this field must be set explicitly.
*
* @param fieldName The name of the field in which the delegate should be stored.
* @param fieldType The type of the field and thus the type of which the delegate is assumed to be of.
* @return A corresponding instrumentation.
*/
public static Instrumentation toInstanceField(String fieldName, Class> fieldType) {
return toInstanceField(fieldName, new TypeDescription.ForLoadedType(nonNull(fieldType)));
}
/**
* Forwards all intercepted method invocations to an instance field of the instrumented class. The value
* of this field must be set explicitly.
*
* @param fieldName The name of the field in which the delegate should be stored.
* @param fieldType The type of the field and thus the type of which the delegate is assumed to be of.
* @return A corresponding instrumentation.
*/
public static Instrumentation toInstanceField(String fieldName, TypeDescription fieldType) {
return new Forwarding(isValidIdentifier(fieldName),
nonNull(fieldType),
PreparationHandler.ForInstanceField.INSTANCE);
}
@Override
public ByteCodeAppender appender(Target instrumentationTarget) {
return new Appender(loadDelegate(instrumentationTarget.getTypeDescription()));
}
/**
* Loads the field onto the operand stack.
*
* @param instrumentedType The instrumented type that declares the field.
* @return A stack manipulation for loading the field value onto the operand stack.
*/
private StackManipulation loadDelegate(TypeDescription instrumentedType) {
return new StackManipulation.Compound(preparationHandler.loadFieldOwner(),
FieldAccess.forField(instrumentedType.getDeclaredFields()
.filter((named(fieldName))).getOnly()).getter());
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return preparationHandler.prepare(instrumentedType, fieldName, fieldType);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Forwarding that = (Forwarding) other;
return fieldName.equals(that.fieldName)
&& fieldType.equals(that.fieldType)
&& preparationHandler.equals(that.preparationHandler);
}
@Override
public int hashCode() {
int result = fieldName.hashCode();
result = 31 * result + fieldType.hashCode();
result = 31 * result + preparationHandler.hashCode();
return result;
}
@Override
public String toString() {
return "Forwarding{" +
"fieldName='" + fieldName + '\'' +
", fieldType=" + fieldType +
", preparationHandler=" + preparationHandler +
'}';
}
/**
* A handler for preparing a {@link com.ui4j.bytebuddy.instrumentation.Forwarding} instrumentation.
*/
protected interface PreparationHandler {
/**
* Prepares the instrumented type.
*
* @param instrumentedType The instrumented type to prepare.
* @param fieldName The name of the field in which the delegate should be stored.
* @param fieldType The type of the field.
* @return The prepared instrumented type.
*/
InstrumentedType prepare(InstrumentedType instrumentedType, String fieldName, TypeDescription fieldType);
/**
* Creates a stack manipulation for loading the field owner onto the operand stack.
*
* @return A stack manipulation for loading the field owner onto the operand stack.
*/
StackManipulation loadFieldOwner();
/**
* A preparation handler for an unset instance that is stored in an instance field.
*/
static enum ForInstanceField implements PreparationHandler {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType, String fieldName, TypeDescription fieldType) {
return instrumentedType.withField(fieldName, fieldType, Opcodes.ACC_PRIVATE);
}
@Override
public StackManipulation loadFieldOwner() {
return MethodVariableAccess.REFERENCE.loadOffset(0);
}
}
/**
* A preparation handler for an unset instance that is stored in a {@code static} field.
*/
static enum ForStaticField implements PreparationHandler {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType, String fieldName, TypeDescription fieldType) {
return instrumentedType.withField(fieldName, fieldType, Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC);
}
@Override
public StackManipulation loadFieldOwner() {
return StackManipulation.LegalTrivial.INSTANCE;
}
}
/**
* A preparation handler for an explicit instance that is stored in a {@code static} field.
*/
static class ForStaticInstance implements PreparationHandler {
/**
* The target of the delegation.
*/
private final Object target;
/**
* Creates a new preparation handler for an explicit instance.
*
* @param target The target of the delegation.
*/
public ForStaticInstance(Object target) {
this.target = target;
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType, String fieldName, TypeDescription fieldType) {
return instrumentedType
.withField(fieldName, fieldType, Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)
.withInitializer(new LoadedTypeInitializer.ForStaticField