org.testifyproject.bytebuddy.dynamic.scaffold.inline.MethodRebaseResolver Maven / Gradle / Ivy
The newest version!
package org.testifyproject.bytebuddy.dynamic.scaffold.inline;
import lombok.EqualsAndHashCode;
import org.testifyproject.bytebuddy.ClassFileVersion;
import org.testifyproject.bytebuddy.description.annotation.AnnotationList;
import org.testifyproject.bytebuddy.description.annotation.AnnotationValue;
import org.testifyproject.bytebuddy.description.method.MethodDescription;
import org.testifyproject.bytebuddy.description.method.ParameterDescription;
import org.testifyproject.bytebuddy.description.method.ParameterList;
import org.testifyproject.bytebuddy.description.type.TypeDescription;
import org.testifyproject.bytebuddy.description.type.TypeList;
import org.testifyproject.bytebuddy.dynamic.DynamicType;
import org.testifyproject.bytebuddy.implementation.MethodAccessorFactory;
import org.testifyproject.bytebuddy.implementation.auxiliary.AuxiliaryType;
import org.testifyproject.bytebuddy.implementation.auxiliary.TrivialType;
import org.testifyproject.bytebuddy.implementation.bytecode.StackManipulation;
import org.testifyproject.bytebuddy.implementation.bytecode.constant.NullConstant;
import org.testifyproject.bytebuddy.utility.CompoundList;
import org.testifyproject.bytebuddy.jar.asm.Opcodes;
import java.util.*;
import static org.testifyproject.bytebuddy.matcher.ElementMatchers.is;
/**
* A method rebase resolver is responsible for mapping methods of an instrumented type to an alternative signature.
* This way a method can exist in two versions within a class:
*
* - The rebased method which represents the original implementation as it is present in a class file.
* - An overriden method which implements user code which is still able to invoke the original, rebased method.
*
*/
public interface MethodRebaseResolver {
/**
* Checks if a method is eligible for rebasing and resolves this possibly rebased method.
*
* @param methodDescription A description of the method to resolve.
* @return A resolution for the given method.
*/
Resolution resolve(MethodDescription.InDefinedShape methodDescription);
/**
* Returns a (potentially empty) list of auxiliary types that are required by this method rebase resolver.
*
* @return A list of auxiliary types that are required by this method rebase resolver.
*/
List getAuxiliaryTypes();
/**
* Returns a map of all rebasable methods' signature tokens to their resolution.
*
* @return A map of all rebasable methods' signature tokens to their resolution.
*/
Map asTokenMap();
/**
* A method rebase resolver that preserves any method in its original form.
*/
enum Disabled implements MethodRebaseResolver {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public Resolution resolve(MethodDescription.InDefinedShape methodDescription) {
return new Resolution.Preserved(methodDescription);
}
@Override
public List getAuxiliaryTypes() {
return Collections.emptyList();
}
@Override
public Map asTokenMap() {
return Collections.emptyMap();
}
}
/**
* A resolution for a method that was checked by a {@link MethodRebaseResolver}.
*/
interface Resolution {
/**
* Checks if this resolution represents a rebased method.
*
* @return {@code true} if this resolution requires to rebase a method.
*/
boolean isRebased();
/**
* Returns the resolved method if this resolution represents a rebased method or the original method.
*
* @return The resolved method if this resolution represents a rebased method or the original method.
*/
MethodDescription.InDefinedShape getResolvedMethod();
/**
* A rebased method might require additional arguments in order to create a distinct signature. The
* stack manipulation that is returned from this method loads these arguments onto the operand stack. For
* a non-rebased method, this method throws an {@link java.lang.IllegalArgumentException}.
*
* @return A stack manipulation that loaded the additional arguments onto the stack, if any.
*/
StackManipulation getAdditionalArguments();
/**
* A {@link MethodRebaseResolver.Resolution} of a non-rebased method.
*/
@EqualsAndHashCode
class Preserved implements Resolution {
/**
* The preserved method.
*/
private final MethodDescription.InDefinedShape methodDescription;
/**
* Creates a new {@link MethodRebaseResolver.Resolution} for
* a non-rebased method.
*
* @param methodDescription The preserved method.
*/
public Preserved(MethodDescription.InDefinedShape methodDescription) {
this.methodDescription = methodDescription;
}
@Override
public boolean isRebased() {
return false;
}
@Override
public MethodDescription.InDefinedShape getResolvedMethod() {
return methodDescription;
}
@Override
public StackManipulation getAdditionalArguments() {
throw new IllegalStateException("Cannot process additional arguments for non-rebased method: " + methodDescription);
}
}
/**
* A {@link MethodRebaseResolver.Resolution} of a rebased method.
*/
@EqualsAndHashCode
class ForRebasedMethod implements Resolution {
/**
* The rebased method.
*/
private final MethodDescription.InDefinedShape methodDescription;
/**
* Creates a resolution for a rebased method.
*
* @param methodDescription The rebased method.
*/
protected ForRebasedMethod(MethodDescription.InDefinedShape methodDescription) {
this.methodDescription = methodDescription;
}
/**
* Resolves a rebasement for the provided method.
*
* @param instrumentedType The instrumented type.
* @param methodDescription The method to be rebased.
* @param methodNameTransformer The transformer to use for renaming the method.
* @return A resolution for rebasing the provided method.
*/
public static Resolution of(TypeDescription instrumentedType,
MethodDescription.InDefinedShape methodDescription,
MethodNameTransformer methodNameTransformer) {
return new ForRebasedMethod(new RebasedMethod(instrumentedType, methodDescription, methodNameTransformer));
}
@Override
public boolean isRebased() {
return true;
}
@Override
public MethodDescription.InDefinedShape getResolvedMethod() {
return methodDescription;
}
@Override
public StackManipulation getAdditionalArguments() {
return StackManipulation.Trivial.INSTANCE;
}
/**
* A description of a rebased method.
*/
protected static class RebasedMethod extends MethodDescription.InDefinedShape.AbstractBase {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The method that is being rebased.
*/
private final InDefinedShape methodDescription;
/**
* The transformer to use for renaming the method.
*/
private final MethodNameTransformer methodNameTransformer;
/**
* Creates a new rebased method.
*
* @param instrumentedType The instrumented type.
* @param methodDescription The method that is being rebased.
* @param methodNameTransformer The transformer to use for renaming the method.
*/
protected RebasedMethod(TypeDescription instrumentedType, InDefinedShape methodDescription, MethodNameTransformer methodNameTransformer) {
this.instrumentedType = instrumentedType;
this.methodDescription = methodDescription;
this.methodNameTransformer = methodNameTransformer;
}
@Override
public TypeDescription.Generic getReturnType() {
return methodDescription.getReturnType().asRawType();
}
@Override
public ParameterList getParameters() {
return new ParameterList.Explicit.ForTypes(this, methodDescription.getParameters().asTypeList().asRawTypes());
}
@Override
public TypeList.Generic getExceptionTypes() {
return methodDescription.getExceptionTypes().asRawTypes();
}
@Override
public AnnotationValue, ?> getDefaultValue() {
return AnnotationValue.UNDEFINED;
}
@Override
public TypeList.Generic getTypeVariables() {
return new TypeList.Generic.Empty();
}
@Override
public AnnotationList getDeclaredAnnotations() {
return new AnnotationList.Empty();
}
@Override
public TypeDescription getDeclaringType() {
return methodDescription.getDeclaringType();
}
@Override
public int getModifiers() {
return Opcodes.ACC_SYNTHETIC
| (methodDescription.isStatic() ? Opcodes.ACC_STATIC : EMPTY_MASK)
| (methodDescription.isNative() ? Opcodes.ACC_NATIVE : EMPTY_MASK)
| (instrumentedType.isInterface() ? Opcodes.ACC_PUBLIC : Opcodes.ACC_PRIVATE);
}
@Override
public String getInternalName() {
return methodNameTransformer.transform(methodDescription);
}
}
}
/**
* A {@link MethodRebaseResolver.Resolution} of a rebased constructor.
*/
@EqualsAndHashCode
class ForRebasedConstructor implements Resolution {
/**
* The rebased constructor.
*/
private final MethodDescription.InDefinedShape methodDescription;
/**
* Creates a new resolution for a rebased constructor.
*
* @param methodDescription The rebased constructor.
*/
protected ForRebasedConstructor(MethodDescription.InDefinedShape methodDescription) {
this.methodDescription = methodDescription;
}
/**
* Resolves a constructor rebasement.
*
* @param methodDescription The constructor to rebase.
* @param placeholderType The placeholder type to use to distinguish the constructor's signature.
* @return A resolution of the provided constructor.
*/
public static Resolution of(MethodDescription.InDefinedShape methodDescription, TypeDescription placeholderType) {
return new ForRebasedConstructor(new RebasedConstructor(methodDescription, placeholderType));
}
@Override
public boolean isRebased() {
return true;
}
@Override
public MethodDescription.InDefinedShape getResolvedMethod() {
return methodDescription;
}
@Override
public StackManipulation getAdditionalArguments() {
return NullConstant.INSTANCE;
}
/**
* An description of a rebased constructor.
*/
protected static class RebasedConstructor extends MethodDescription.InDefinedShape.AbstractBase {
/**
* The constructor that is rebased.
*/
private final InDefinedShape methodDescription;
/**
* The placeholder type that is used to distinguish the constructor's signature.
*/
private final TypeDescription placeholderType;
/**
* Creates a new rebased constructor.
*
* @param methodDescription The constructor that is rebased.
* @param placeholderType The placeholder type that is used to distinguish the constructor's signature.
*/
protected RebasedConstructor(InDefinedShape methodDescription, TypeDescription placeholderType) {
this.methodDescription = methodDescription;
this.placeholderType = placeholderType;
}
@Override
public TypeDescription.Generic getReturnType() {
return TypeDescription.Generic.VOID;
}
@Override
public ParameterList getParameters() {
return new ParameterList.Explicit.ForTypes(this, CompoundList.of(methodDescription.getParameters().asTypeList().asErasures(), placeholderType));
}
@Override
public TypeList.Generic getExceptionTypes() {
return methodDescription.getExceptionTypes().asRawTypes();
}
@Override
public AnnotationValue, ?> getDefaultValue() {
return AnnotationValue.UNDEFINED;
}
@Override
public TypeList.Generic getTypeVariables() {
return new TypeList.Generic.Empty();
}
@Override
public AnnotationList getDeclaredAnnotations() {
return new AnnotationList.Empty();
}
@Override
public TypeDescription getDeclaringType() {
return methodDescription.getDeclaringType();
}
@Override
public int getModifiers() {
return Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PRIVATE;
}
@Override
public String getInternalName() {
return MethodDescription.CONSTRUCTOR_INTERNAL_NAME;
}
}
}
}
/**
* A default implementation of a method rebase resolver.
*/
@EqualsAndHashCode
class Default implements MethodRebaseResolver {
/**
* A mapping of rebased methods to their existing resolutions.
*/
private final Map resolutions;
/**
* A list of dynamic types that need to be appended to the created type in order to allow for the rebasement.
*/
private final List dynamicTypes;
/**
* Creates a new default method rebased resolver.
*
* @param resolutions A mapping of rebased methods to their existing resolutions.
* @param dynamicTypes A list of dynamic types that need to be appended to the created type in order to allow for the rebasement.
*/
protected Default(Map resolutions, List dynamicTypes) {
this.resolutions = resolutions;
this.dynamicTypes = dynamicTypes;
}
/**
* Creates a new method rebase resolver.
*
* @param instrumentedType The instrumented type.
* @param rebaseableMethodTokens Tokens describing all methods that can possibly be rebased.
* @param classFileVersion The class file version for the instrumentation.
* @param auxiliaryTypeNamingStrategy The naming strategy for naming a potential auxiliary type.
* @param methodNameTransformer A transformer for method names.
* @return A method rebase resolver that is capable of rebasing any of the provided methods.
*/
public static MethodRebaseResolver make(TypeDescription instrumentedType,
Set extends MethodDescription.Token> rebaseableMethodTokens,
ClassFileVersion classFileVersion,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
MethodNameTransformer methodNameTransformer) {
DynamicType placeholderType = null;
Map resolutions = new HashMap();
for (MethodDescription.InDefinedShape instrumentedMethod : instrumentedType.getDeclaredMethods()) {
if (rebaseableMethodTokens.contains(instrumentedMethod.asToken(is(instrumentedType)))) {
Resolution resolution;
if (instrumentedMethod.isConstructor()) {
if (placeholderType == null) {
placeholderType = TrivialType.SIGNATURE_RELEVANT.make(auxiliaryTypeNamingStrategy.name(instrumentedType),
classFileVersion,
MethodAccessorFactory.Illegal.INSTANCE);
}
resolution = Resolution.ForRebasedConstructor.of(instrumentedMethod, placeholderType.getTypeDescription());
} else {
resolution = Resolution.ForRebasedMethod.of(instrumentedType, instrumentedMethod, methodNameTransformer);
}
resolutions.put(instrumentedMethod, resolution);
}
}
return placeholderType == null
? new Default(resolutions, Collections.emptyList())
: new Default(resolutions, Collections.singletonList(placeholderType));
}
@Override
public Resolution resolve(MethodDescription.InDefinedShape methodDescription) {
Resolution resolution = resolutions.get(methodDescription);
return resolution == null
? new Resolution.Preserved(methodDescription)
: resolution;
}
@Override
public List getAuxiliaryTypes() {
return dynamicTypes;
}
@Override
public Map asTokenMap() {
Map tokenMap = new HashMap();
for (Map.Entry entry : resolutions.entrySet()) {
tokenMap.put(entry.getKey().asSignatureToken(), entry.getValue());
}
return tokenMap;
}
}
}