com.ui4j.bytebuddy.dynamic.scaffold.inline.MethodRebaseResolver Maven / Gradle / Ivy
The newest version!
package com.ui4j.bytebuddy.dynamic.scaffold.inline;
import com.ui4j.bytebuddy.instrumentation.method.MethodDescription;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.StackManipulation;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.constant.NullConstant;
import com.ui4j.bytebuddy.instrumentation.type.TypeDescription;
import com.ui4j.bytebuddy.matcher.ElementMatcher;
import com.ui4j.bytebuddy.jar.asm.Opcodes;
import static com.ui4j.bytebuddy.utility.ByteBuddyCommons.join;
/**
* 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 {
/**
* The modifier that is used for rebased methods.
*/
static final int REBASED_METHOD_MODIFIER = Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC;
/**
* 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 methodDescription);
/**
* A method rebase resolver that preserves any method in its original form.
*/
static enum NoOp implements MethodRebaseResolver {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public Resolution resolve(MethodDescription methodDescription) {
return new Resolution.Preserved(methodDescription);
}
}
/**
* A method name transformer provides a unique mapping of a method's name to an alternative name.
*
* @see MethodRebaseResolver
*/
static interface MethodNameTransformer {
/**
* Transforms a method's name to an alternative name. For a given argument, this mapper must always provide
* the same return value.
*
* @param originalName The original name.
* @return The alternative name.
*/
String transform(String originalName);
/**
* A method name transformer that adds a fixed suffix to an original method name, separated by a {@code $}.
*/
static class Suffixing implements MethodNameTransformer {
/**
* The default suffix to add to an original method name.
*/
private static final String DEFAULT_SUFFIX = "original";
/**
* The suffix to append to a method name.
*/
private final String suffix;
/**
* Creates a new suffixing method name transformer which adds a default suffix.
*/
public Suffixing() {
this(DEFAULT_SUFFIX);
}
/**
* Creates a new suffixing method name transformer.
*
* @param suffix The suffix to add to the method name before the seed.
*/
public Suffixing(String suffix) {
this.suffix = suffix;
}
@Override
public String transform(String originalName) {
return String.format("%s$%s", originalName, suffix);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& suffix.equals(((Suffixing) other).suffix);
}
@Override
public int hashCode() {
return suffix.hashCode();
}
@Override
public String toString() {
return "MethodRebaseResolver.MethodNameTransformer.Suffixing{" +
"suffix='" + suffix + '\'' +
'}';
}
}
/**
* A method name transformer that adds a fixed prefix to an original method name.
*/
static class Prefixing implements MethodNameTransformer {
/**
* The default prefix to add to an original method name.
*/
private static final String DEFAULT_PREFIX = "original";
/**
* The prefix that is appended.
*/
private final String prefix;
/**
* Creates a new prefixing method name transformer using a default prefix.
*/
public Prefixing() {
this(DEFAULT_PREFIX);
}
/**
* Creates a new prefixing method name transformer.
*
* @param prefix The prefix being used.
*/
public Prefixing(String prefix) {
this.prefix = prefix;
}
@Override
public String transform(String originalName) {
return String.format("%s%s", prefix, originalName);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& prefix.equals(((Prefixing) other).prefix);
}
@Override
public int hashCode() {
return prefix.hashCode();
}
@Override
public String toString() {
return "MethodRebaseResolver.MethodNameTransformer.Prefixing{" +
"prefix='" + prefix + '\'' +
'}';
}
}
}
/**
* A resolution for a method that was checked by a {@link MethodRebaseResolver}.
*/
static 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 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.
*/
static class Preserved implements Resolution {
/**
* The preserved method.
*/
private final MethodDescription methodDescription;
/**
* Creates a new {@link MethodRebaseResolver.Resolution} for
* a non-rebased method.
*
* @param methodDescription The preserved method.
*/
public Preserved(MethodDescription methodDescription) {
this.methodDescription = methodDescription;
}
@Override
public boolean isRebased() {
return false;
}
@Override
public MethodDescription getResolvedMethod() {
return methodDescription;
}
@Override
public StackManipulation getAdditionalArguments() {
throw new IllegalStateException("A non-rebased method never requires additional arguments");
}
@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.
*/
static class ForRebasedMethod implements Resolution {
/**
* The rebased method.
*/
private final MethodDescription methodDescription;
/**
* Creates a {@link MethodRebaseResolver.Resolution} for a
* rebased method.
*
* @param methodDescription The original method that should be rebased.
* @param methodNameTransformer A transformer for renaming a rebased method.
*/
public ForRebasedMethod(MethodDescription methodDescription, MethodNameTransformer methodNameTransformer) {
this.methodDescription = new MethodDescription.Latent(
methodNameTransformer.transform(methodDescription.getInternalName()),
methodDescription.getDeclaringType(),
methodDescription.getReturnType(),
methodDescription.getParameters().asTypeList(),
REBASED_METHOD_MODIFIER
| (methodDescription.isStatic() ? Opcodes.ACC_STATIC : 0)
| (methodDescription.isNative() ? Opcodes.ACC_NATIVE : 0),
methodDescription.getExceptionTypes());
}
@Override
public boolean isRebased() {
return true;
}
@Override
public MethodDescription getResolvedMethod() {
return methodDescription;
}
@Override
public StackManipulation getAdditionalArguments() {
return StackManipulation.LegalTrivial.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 {@link MethodRebaseResolver.Resolution} of a rebased constructor.
*/
static class ForRebasedConstructor implements Resolution {
/**
* The rebased constructor.
*/
private final MethodDescription methodDescription;
/**
* Creates a {@link MethodRebaseResolver.Resolution} for a
* rebased method.
*
* @param methodDescription The constructor to rebase.
* @param placeholderType A placeholder type which is added to a rebased constructor.
*/
public ForRebasedConstructor(MethodDescription methodDescription, TypeDescription placeholderType) {
this.methodDescription = new MethodDescription.Latent(methodDescription.getInternalName(),
methodDescription.getDeclaringType(),
methodDescription.getReturnType(),
join(methodDescription.getParameters().asTypeList(), placeholderType),
REBASED_METHOD_MODIFIER,
methodDescription.getExceptionTypes());
}
@Override
public boolean isRebased() {
return true;
}
@Override
public MethodDescription 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 + '}';
}
}
}
/**
* A default implementation of a {@link MethodRebaseResolver} which
* renames rebased methods and adds an additional constructor placeholder parameter to constructors. Ignored
* methods are never rebased.
*/
static class Default implements MethodRebaseResolver {
/**
* Ignored methods which are never rebased.
*/
private final ElementMatcher super MethodDescription> ignoredMethods;
/**
* A placeholder type which is added to a rebased constructor.
*/
private final TypeDescription placeholderType;
/**
* A transformer for renaming a rebased method.
*/
private final MethodNameTransformer methodNameTransformer;
/**
* Creates a default method rebase resolver.
*
* @param ignoredMethods Ignored methods which are never rebased.
* @param placeholderType A placeholder type which is added to a rebased constructor.
* @param methodNameTransformer A transformer for renaming a rebased method.
*/
public Default(ElementMatcher super MethodDescription> ignoredMethods,
TypeDescription placeholderType,
MethodNameTransformer methodNameTransformer) {
this.ignoredMethods = ignoredMethods;
this.placeholderType = placeholderType;
this.methodNameTransformer = methodNameTransformer;
}
@Override
public Resolution resolve(MethodDescription methodDescription) {
return ignoredMethods.matches(methodDescription)
? new Resolution.Preserved(methodDescription)
: rebase(methodDescription);
}
/**
* Resolves a rebase method of a given method.
*
* @param methodDescription The method to rebase.
* @return The resolution for the given method.
*/
private Resolution rebase(MethodDescription methodDescription) {
return methodDescription.isConstructor()
? new Resolution.ForRebasedConstructor(methodDescription, placeholderType)
: new Resolution.ForRebasedMethod(methodDescription, methodNameTransformer);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Default aDefault = (Default) other;
return ignoredMethods.equals(aDefault.ignoredMethods)
&& placeholderType.equals(aDefault.placeholderType)
&& methodNameTransformer.equals(aDefault.methodNameTransformer);
}
@Override
public int hashCode() {
int result = ignoredMethods.hashCode();
result = 31 * result + placeholderType.hashCode();
result = 31 * result + methodNameTransformer.hashCode();
return result;
}
@Override
public String toString() {
return "MethodRebaseResolver.Default{" +
"ignoredMethods=" + ignoredMethods +
", placeholderType=" + placeholderType +
", methodNameTransformer=" + methodNameTransformer +
'}';
}
}
}