com.fitbur.bytebuddy.dynamic.scaffold.inline.MethodRebaseResolver Maven / Gradle / Ivy
package com.fitbur.bytebuddy.dynamic.scaffold.inline;
import com.fitbur.bytebuddy.ClassFileVersion;
import com.fitbur.bytebuddy.description.annotation.AnnotationList;
import com.fitbur.bytebuddy.description.method.MethodDescription;
import com.fitbur.bytebuddy.description.method.ParameterDescription;
import com.fitbur.bytebuddy.description.method.ParameterList;
import com.fitbur.bytebuddy.description.type.TypeDescription;
import com.fitbur.bytebuddy.description.type.TypeList;
import com.fitbur.bytebuddy.dynamic.DynamicType;
import com.fitbur.bytebuddy.implementation.auxiliary.AuxiliaryType;
import com.fitbur.bytebuddy.implementation.auxiliary.TrivialType;
import com.fitbur.bytebuddy.implementation.bytecode.StackManipulation;
import com.fitbur.bytebuddy.implementation.bytecode.constant.NullConstant;
import com.fitbur.bytebuddy.utility.CompoundList;
import com.fitbur.bytebuddy.jar.asm.Opcodes;
import java.util.*;
import static com.fitbur.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();
}
@Override
public String toString() {
return "MethodRebaseResolver.Disabled." + name();
}
}
/**
* 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.
*/
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);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& methodDescription.equals(((Preserved) other).methodDescription);
}
@Override
public int hashCode() {
return methodDescription.hashCode();
}
@Override
public String toString() {
return "MethodRebaseResolver.Resolution.Preserved{methodDescription=" + methodDescription + '}';
}
}
/**
* A {@link MethodRebaseResolver.Resolution} of a rebased method.
*/
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 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(MethodDescription.InDefinedShape methodDescription, MethodNameTransformer methodNameTransformer) {
return new ForRebasedMethod(new RebasedMethod(methodDescription, methodNameTransformer));
}
@Override
public boolean isRebased() {
return true;
}
@Override
public MethodDescription.InDefinedShape getResolvedMethod() {
return methodDescription;
}
@Override
public StackManipulation getAdditionalArguments() {
return StackManipulation.Trivial.INSTANCE;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& methodDescription.equals(((ForRebasedMethod) other).methodDescription);
}
@Override
public int hashCode() {
return methodDescription.hashCode();
}
@Override
public String toString() {
return "MethodRebaseResolver.Resolution.ForRebasedMethod{methodDescription=" + methodDescription + '}';
}
/**
* A description of a rebased method.
*/
protected static class RebasedMethod extends MethodDescription.InDefinedShape.AbstractBase {
/**
* 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 methodDescription The method that is being rebased.
* @param methodNameTransformer The transformer to use for renaming the method.
*/
protected RebasedMethod(InDefinedShape methodDescription, MethodNameTransformer methodNameTransformer) {
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 Object getDefaultValue() {
return MethodDescription.NO_DEFAULT_VALUE;
}
@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)
| (methodDescription.getDeclaringType().isInterface() ? Opcodes.ACC_PUBLIC : Opcodes.ACC_PRIVATE);
}
@Override
public String getInternalName() {
return methodNameTransformer.transform(methodDescription);
}
}
}
/**
* A {@link MethodRebaseResolver.Resolution} of a rebased constructor.
*/
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;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& methodDescription.equals(((ForRebasedConstructor) other).methodDescription);
}
@Override
public int hashCode() {
return methodDescription.hashCode();
}
@Override
public String toString() {
return "MethodRebaseResolver.Resolution.ForRebasedConstructor{methodDescription=" + methodDescription + '}';
}
/**
* 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 Object getDefaultValue() {
return MethodDescription.NO_DEFAULT_VALUE;
}
@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.
*/
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,
AuxiliaryType.MethodAccessorFactory.Illegal.INSTANCE);
}
resolution = Resolution.ForRebasedConstructor.of(instrumentedMethod, placeholderType.getTypeDescription());
} else {
resolution = Resolution.ForRebasedMethod.of(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;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& resolutions.equals(((Default) other).resolutions)
&& dynamicTypes.equals(((Default) other).dynamicTypes);
}
@Override
public int hashCode() {
return 31 * resolutions.hashCode() + dynamicTypes.hashCode();
}
@Override
public String toString() {
return "MethodRebaseResolver.Default{" +
"resolutions=" + resolutions +
", dynamicTypes=" + dynamicTypes +
'}';
}
}
}