net.bytebuddy.asm.MemberSubstitution Maven / Gradle / Ivy
/*
* Copyright 2014 - Present Rafael Winterhalter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bytebuddy.asm;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.description.ByteCodeElement;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.annotation.AnnotationValue;
import net.bytebuddy.description.enumeration.EnumerationDescription;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.description.type.TypeVariableToken;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.TargetType;
import net.bytebuddy.dynamic.scaffold.FieldLocator;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bytecode.*;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
import net.bytebuddy.implementation.bytecode.constant.*;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.*;
import net.bytebuddy.utility.nullability.MaybeNull;
import net.bytebuddy.utility.visitor.LocalVariableAwareMethodVisitor;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import java.io.Serializable;
import java.lang.annotation.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import static net.bytebuddy.matcher.ElementMatchers.*;
/**
*
* Substitutes field access, method invocations or constructor calls within a method's body.
*
*
* Note: This substitution must not be used to match constructor calls to an instrumented class's super constructor invocation from
* within a constructor. Matching such constructors will result in an invalid stack and a verification error.
*
*
* Note: This visitor will compute the required stack size on a best effort basis. For allocating an optimal stack size, ASM needs
* to be configured to compute the stack size.
*
*
* Important: This component relies on using a {@link TypePool} for locating types within method bodies. Within a redefinition
* or a rebasement, this type pool normally resolved correctly by Byte Buddy. When subclassing a type, the type pool must be set
* explicitly, using {@link net.bytebuddy.dynamic.DynamicType.Builder#make(TypePool)} or any similar method. It is however not normally
* necessary to use this component when subclassing a type where methods are only defined explicitly.
*
*/
@HashCodeAndEqualsPlugin.Enhance
public class MemberSubstitution implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {
/**
* The index of the this reference within a non-static method.
*/
protected static final int THIS_REFERENCE = 0;
/**
* The method graph compiler to use.
*/
private final MethodGraph.Compiler methodGraphCompiler;
/**
* The type pool resolver to use.
*/
private final TypePoolResolver typePoolResolver;
/**
* {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
*/
private final boolean strict;
/**
* {@code true} if the instrumentation should fail if applied to a method without match.
*/
private final boolean failIfNoMatch;
/**
* The replacement factory to use.
*/
private final Replacement.Factory replacementFactory;
/**
* Creates a default member substitution.
*
* @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
*/
protected MemberSubstitution(boolean strict) {
this(MethodGraph.Compiler.DEFAULT, TypePoolResolver.OfImplicitPool.INSTANCE, strict, false, Replacement.NoOp.INSTANCE);
}
/**
* Creates a new member substitution.
*
* @param methodGraphCompiler The method graph compiler to use.
* @param typePoolResolver The type pool resolver to use.
* @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
* @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match.
* @param replacementFactory The replacement factory to use.
*/
protected MemberSubstitution(MethodGraph.Compiler methodGraphCompiler,
TypePoolResolver typePoolResolver,
boolean strict,
boolean failIfNoMatch,
Replacement.Factory replacementFactory) {
this.methodGraphCompiler = methodGraphCompiler;
this.typePoolResolver = typePoolResolver;
this.failIfNoMatch = failIfNoMatch;
this.strict = strict;
this.replacementFactory = replacementFactory;
}
/**
* Creates a member substitution that requires the resolution of all fields and methods that are referenced within a method body. Doing so,
* this component raises an exception if any member cannot be resolved what makes this component unusable when facing optional types.
*
* @return A strict member substitution.
*/
public static MemberSubstitution strict() {
return new MemberSubstitution(true);
}
/**
* Creates a member substitution that skips any unresolvable fields or methods that are referenced within a method body. Using a relaxed
* member substitution, methods containing optional types are supported. In the process, it is however possible that misconfigurations
* of this component remain undiscovered.
*
* @return A relaxed member substitution.
*/
public static MemberSubstitution relaxed() {
return new MemberSubstitution(false);
}
/**
* Substitutes any interaction with a field or method that matches the given matcher.
*
* @param matcher The matcher to determine what access to byte code elements to substitute.
* @return A specification that allows to determine how to substitute any interaction with byte code elements that match the supplied matcher.
*/
public WithoutSpecification element(ElementMatcher super ByteCodeElement.Member> matcher) {
return new WithoutSpecification.ForMatchedByteCodeElement(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher);
}
/**
* Substitutes any field access that matches the given matcher.
*
* @param matcher The matcher to determine what fields to substitute.
* @return A specification that allows to determine how to substitute any field access that match the supplied matcher.
*/
public WithoutSpecification.ForMatchedField field(ElementMatcher super FieldDescription> matcher) {
return new WithoutSpecification.ForMatchedField(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher);
}
/**
* Substitutes any method invocation that matches the given matcher.
*
* @param matcher The matcher to determine what methods to substitute.
* @return A specification that allows to determine how to substitute any method invocations that match the supplied matcher.
*/
public WithoutSpecification.ForMatchedMethod method(ElementMatcher super MethodDescription> matcher) {
return new WithoutSpecification.ForMatchedMethod(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher);
}
/**
* Substitutes any constructor invocation that matches the given matcher.
*
* @param matcher The matcher to determine what constructors to substitute.
* @return A specification that allows to determine how to substitute any constructor invocations that match the supplied matcher.
*/
public WithoutSpecification constructor(ElementMatcher super MethodDescription> matcher) {
return invokable(isConstructor().and(matcher));
}
/**
* Substitutes any method or constructor invocation that matches the given matcher.
*
* @param matcher The matcher to determine what method or constructors to substitute.
* @return A specification that allows to determine how to substitute any constructor invocations that match the supplied matcher.
*/
public WithoutSpecification invokable(ElementMatcher super MethodDescription> matcher) {
return new WithoutSpecification.ForMatchedMethod(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher);
}
/**
* Specifies the use of a specific method graph compiler for the resolution of virtual methods.
*
* @param methodGraphCompiler The method graph compiler to use.
* @return A new member substitution that is equal to this but uses the specified method graph compiler.
*/
public MemberSubstitution with(MethodGraph.Compiler methodGraphCompiler) {
return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory);
}
/**
* Specifies a type pool resolver to be used for locating members.
*
* @param typePoolResolver The type pool resolver to use.
* @return A new instance of this member substitution that uses the supplied type pool resolver.
*/
public MemberSubstitution with(TypePoolResolver typePoolResolver) {
return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory);
}
/**
* Specifies if this substitution should fail if applied on a method without a match.
* @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match.
* @return A new instance of this member substitution that fails if applied on a method without a match.
*/
public MemberSubstitution failIfNoMatch(boolean failIfNoMatch) {
return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory);
}
/**
* Applies this member substitution to any method that matches the supplied matcher.
*
* @param matcher The matcher to determine this substitutions application.
* @return An ASM visitor wrapper that applies all specified substitutions for any matched method.
*/
public AsmVisitorWrapper.ForDeclaredMethods on(ElementMatcher super MethodDescription> matcher) {
return new AsmVisitorWrapper.ForDeclaredMethods().invokable(matcher, this);
}
/**
* {@inheritDoc}
*/
public MethodVisitor wrap(TypeDescription instrumentedType,
MethodDescription instrumentedMethod,
MethodVisitor methodVisitor,
Implementation.Context implementationContext,
TypePool typePool,
int writerFlags,
int readerFlags) {
typePool = typePoolResolver.resolve(instrumentedType, instrumentedMethod, typePool);
return new SubstitutingMethodVisitor(methodVisitor,
instrumentedType,
instrumentedMethod,
methodGraphCompiler,
strict,
failIfNoMatch,
replacementFactory.make(instrumentedType, instrumentedMethod, typePool),
implementationContext,
typePool,
implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V11));
}
/**
* A member substitution that lacks a specification for how to substitute the matched members references within a method body.
*/
@HashCodeAndEqualsPlugin.Enhance
public abstract static class WithoutSpecification {
/**
* The method graph compiler to use.
*/
protected final MethodGraph.Compiler methodGraphCompiler;
/**
* The type pool resolver to use.
*/
protected final TypePoolResolver typePoolResolver;
/**
* {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
*/
protected final boolean strict;
/**
* {@code true} if the instrumentation should fail if applied to a method without match.
*/
protected final boolean failIfNoMatch;
/**
* The replacement factory to use for creating substitutions.
*/
protected final Replacement.Factory replacementFactory;
/**
* Creates a new member substitution that requires a specification for how to perform a substitution.
*
* @param methodGraphCompiler The method graph compiler to use.
* @param typePoolResolver The type pool resolver to use.
* @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
* @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match.
* @param replacementFactory The replacement factory to use for creating substitutions.
*/
protected WithoutSpecification(MethodGraph.Compiler methodGraphCompiler,
TypePoolResolver typePoolResolver,
boolean strict,
boolean failIfNoMatch,
Replacement.Factory replacementFactory) {
this.methodGraphCompiler = methodGraphCompiler;
this.typePoolResolver = typePoolResolver;
this.strict = strict;
this.failIfNoMatch = failIfNoMatch;
this.replacementFactory = replacementFactory;
}
/**
* Subs any interaction with a matched byte code element. Any value read from the element will be replaced with the stubbed
* value's default, i.e. {@code null} for reference types and the specific {@code 0} value for primitive types. Any written
* value will simply be discarded.
*
* @return A member substitution that stubs any interaction with a matched byte code element.
*/
public MemberSubstitution stub() {
return replaceWith(Substitution.Stubbing.INSTANCE);
}
/**
* Replaces any interaction with a matched byte code element with the provided compile-time constant.
*
* @param value The compile-time constant to set.
* @return A member substitution that replaces any interaction with the supplied compile-time constant.
*/
public MemberSubstitution replaceWithConstant(Object value) {
ConstantValue constant = ConstantValue.Simple.wrap(value);
return replaceWith(new Substitution.ForValue(constant.toStackManipulation(), constant.getTypeDescription().asGenericType()));
}
/**
*
* Replaces any interaction with a matched byte code element by an interaction with the specified field. If a field
* is replacing a method or constructor invocation, it is treated as if it was a field getter or setter respectively.
*
*
* A replacement can only be applied if the field is compatible to the original byte code element, i.e. consumes an
* instance of the declaring type if it is not {@code static} as an argument and consumes or produces an instance of
* the field's type.
*
*
* @param field The field to access instead of interacting with any of the matched byte code elements.
* @return A member substitution that replaces any matched byte code element with an access of the specified field.
*/
public MemberSubstitution replaceWith(Field field) {
return replaceWith(new FieldDescription.ForLoadedField(field));
}
/**
*
* Replaces any interaction with a matched byte code element by an interaction with the specified field. If a field
* is replacing a method or constructor invocation, it is treated as if it was a field getter or setter respectively.
*
*
* A replacement can only be applied if the field is compatible to the original byte code element, i.e. consumes an
* instance of the declaring type if it is not {@code static} as an argument and consumes or produces an instance of
* the field's type.
*
*
* @param fieldDescription The field to access instead of interacting with any of the matched byte code elements.
* @return A member substitution that replaces any matched byte code element with an access of the specified field.
*/
public MemberSubstitution replaceWith(FieldDescription fieldDescription) {
return replaceWith(new Substitution.ForFieldAccess.OfGivenField(fieldDescription));
}
/**
* Replaces any interaction with a matched byte code element with a non-static field access on the first
* parameter of the matched element. When matching a non-static field access or method invocation, the
* substituted field is located on the same receiver type as the original access. For static access, the
* first argument is used as a receiver.
*
* @param matcher A matcher for locating a field on the original interaction's receiver type.
* @return A member substitution that replaces any matched byte code element with an access of the matched field.
*/
public MemberSubstitution replaceWithField(ElementMatcher super FieldDescription> matcher) {
return replaceWith(new Substitution.ForFieldAccess.OfMatchedField(matcher));
}
/**
*
* Replaces any interaction with a matched byte code element by an invocation of the specified method. If a method
* is replacing a field access, it is treated as if it was replacing an invocation of the field's getter or setter respectively.
*
*
* A replacement can only be applied if the method is compatible to the original byte code element, i.e. consumes compatible
* arguments and returns a compatible value. If the method is not {@code static}, it is treated as if {@code this} was an implicit
* first argument.
*
*
* @param method The method to invoke instead of interacting with any of the matched byte code elements.
* @return A member substitution that replaces any matched byte code element with an invocation of the specified method.
*/
public MemberSubstitution replaceWith(Method method) {
return replaceWith(new MethodDescription.ForLoadedMethod(method));
}
/**
*
* Replaces any interaction with a matched byte code element by an invocation of the specified method. If a method
* is replacing a field access, it is treated as if it was replacing an invocation of the field's getter or setter respectively.
*
*
* A replacement can only be applied if the method is compatible to the original byte code element, i.e. consumes compatible
* arguments and returns a compatible value. If the method is not {@code static}, it is treated as if {@code this} was an implicit
* first argument.
*
*
* Important: It is not allowed to specify a constructor or the static type initializer as a replacement.
*
*
* @param methodDescription The method to invoke instead of interacting with any of the matched byte code elements.
* @return A member substitution that replaces any matched byte code element with an invocation of the specified method.
*/
public MemberSubstitution replaceWith(MethodDescription methodDescription) {
if (!methodDescription.isMethod()) {
throw new IllegalArgumentException("Cannot use " + methodDescription + " as a replacement");
}
return replaceWith(new Substitution.ForMethodInvocation.OfGivenMethod(methodDescription));
}
/**
* Replaces any interaction with a matched byte code element with a non-static method access on the first
* parameter of the matched element. When matching a non-static field access or method invocation, the
* substituted method is located on the same receiver type as the original access. For static access, the
* first argument is used as a receiver.
*
* @param matcher A matcher for locating a method on the original interaction's receiver type.
* @return A member substitution that replaces any matched byte code element with an access of the matched method.
*/
public MemberSubstitution replaceWithMethod(ElementMatcher super MethodDescription> matcher) {
return replaceWithMethod(matcher, methodGraphCompiler);
}
/**
* Replaces any interaction with a matched byte code element with a non-static method access on the first
* parameter of the matched element. When matching a non-static field access or method invocation, the
* substituted method is located on the same receiver type as the original access. For static access, the
* first argument is used as a receiver.
*
* @param matcher A matcher for locating a method on the original interaction's receiver type.
* @param methodGraphCompiler The method graph compiler to use for locating a method.
* @return A member substitution that replaces any matched byte code element with an access of the matched method.
*/
public MemberSubstitution replaceWithMethod(ElementMatcher super MethodDescription> matcher, MethodGraph.Compiler methodGraphCompiler) {
return replaceWith(new Substitution.ForMethodInvocation.OfMatchedMethod(matcher, methodGraphCompiler));
}
/**
* Replaces any interaction with a matched byte code element with an invocation of the instrumented
* method. This can cause an infinite recursive call if the arguments to the method are not altered.
*
* @return A member substitution that replaces any matched byte code element with an invocation of the
* instrumented method.
*/
public MemberSubstitution replaceWithInstrumentedMethod() {
return replaceWith(Substitution.ForMethodInvocation.OfInstrumentedMethod.INSTANCE);
}
/**
* Replaces the matched byte code elements with a chain of substitutions that can operate on the same values as the substituted element. This is a
* shortcut for creating a substitution chain with a default assigner.
*
* @param step The steps to apply for a substitution.
* @return A member substitution that replaces any matched byte code element with the provided substitution chain.
*/
public MemberSubstitution replaceWithChain(Substitution.Chain.Step.Factory... step) {
return replaceWithChain(Arrays.asList(step));
}
/**
* Replaces the matched byte code elements with a chain of substitutions that can operate on the same values as the substituted element. This is a
* shortcut for creating a substitution chain with a default assigner.
*
* @param steps The steps to apply for a substitution.
* @return A member substitution that replaces any matched byte code element with the provided substitution chain.
*/
public MemberSubstitution replaceWithChain(List extends Substitution.Chain.Step.Factory> steps) {
return replaceWith(Substitution.Chain.withDefaultAssigner().executing(steps));
}
/**
* Replaces any interaction with the supplied substitution.
*
* @param factory The substitution factory to use for creating the applied substitution.
* @return A member substitution that replaces any matched byte code element with the supplied substitution.
*/
public abstract MemberSubstitution replaceWith(Substitution.Factory factory);
/**
* Describes a member substitution that requires a specification for how to replace a byte code element.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class ForMatchedByteCodeElement extends WithoutSpecification {
/**
* A matcher for any byte code elements that should be substituted.
*/
private final ElementMatcher super ByteCodeElement.Member> matcher;
/**
* Creates a new member substitution for a matched byte code element that requires a specification for how to perform a substitution.
*
* @param methodGraphCompiler The method graph compiler to use.
* @param typePoolResolver The type pool resolver to use.
* @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
* @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match.
* @param replacementFactory The replacement factory to use.
* @param matcher A matcher for any byte code elements that should be substituted.
*/
protected ForMatchedByteCodeElement(MethodGraph.Compiler methodGraphCompiler,
TypePoolResolver typePoolResolver,
boolean strict,
boolean failIfNoMatch,
Replacement.Factory replacementFactory,
ElementMatcher super ByteCodeElement.Member> matcher) {
super(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory);
this.matcher = matcher;
}
/**
* {@inheritDoc}
*/
public MemberSubstitution replaceWith(Substitution.Factory substitutionFactory) {
return new MemberSubstitution(methodGraphCompiler,
typePoolResolver,
strict,
failIfNoMatch,
new Replacement.Factory.Compound(this.replacementFactory, Replacement.ForElementMatchers.Factory.of(matcher, substitutionFactory)));
}
}
/**
* Describes a member substitution that requires a specification for how to replace a field.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class ForMatchedField extends WithoutSpecification {
/**
* A matcher for any field that should be substituted.
*/
private final ElementMatcher super FieldDescription> matcher;
/**
* {@code true} if read access to a field should be substituted.
*/
private final boolean matchRead;
/**
* {@code true} if write access to a field should be substituted.
*/
private final boolean matchWrite;
/**
* Creates a new member substitution for a matched field that requires a specification for how to perform a substitution.
*
* @param methodGraphCompiler The method graph compiler to use.
* @param typePoolResolver The type pool resolver to use.
* @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
* @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match.
* @param replacementFactory The replacement factory to use.
* @param matcher A matcher for any field that should be substituted.
*/
protected ForMatchedField(MethodGraph.Compiler methodGraphCompiler,
TypePoolResolver typePoolResolver,
boolean strict,
boolean failIfNoMatch,
Replacement.Factory replacementFactory,
ElementMatcher super FieldDescription> matcher) {
this(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher, true, true);
}
/**
* Creates a new member substitution for a matched field that requires a specification for how to perform a substitution.
*
* @param methodGraphCompiler The method graph compiler to use.
* @param typePoolResolver The type pool resolver to use.
* @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
* @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match.
* @param replacementFactory The replacement factory to use.
* @param matcher A matcher for any field that should be substituted.
* @param matchRead {@code true} if read access to a field should be substituted.
* @param matchWrite {@code true} if write access to a field should be substituted.
*/
protected ForMatchedField(MethodGraph.Compiler methodGraphCompiler,
TypePoolResolver typePoolResolver,
boolean strict,
boolean failIfNoMatch,
Replacement.Factory replacementFactory,
ElementMatcher super FieldDescription> matcher,
boolean matchRead,
boolean matchWrite) {
super(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory);
this.matcher = matcher;
this.matchRead = matchRead;
this.matchWrite = matchWrite;
}
/**
* When invoked, only read access of the previously matched field is substituted.
*
* @return This instance with the limitation that only read access to the matched field is substituted.
*/
public WithoutSpecification onRead() {
return new ForMatchedField(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher, true, false);
}
/**
* When invoked, only write access of the previously matched field is substituted.
*
* @return This instance with the limitation that only write access to the matched field is substituted.
*/
public WithoutSpecification onWrite() {
return new ForMatchedField(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher, false, true);
}
/**
* {@inheritDoc}
*/
public MemberSubstitution replaceWith(Substitution.Factory substitutionFactory) {
return new MemberSubstitution(methodGraphCompiler,
typePoolResolver,
strict,
failIfNoMatch,
new Replacement.Factory.Compound(this.replacementFactory, Replacement.ForElementMatchers.Factory.ofField(matcher, matchRead, matchWrite, substitutionFactory)));
}
}
/**
* Describes a member substitution that requires a specification for how to replace a method or constructor.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class ForMatchedMethod extends WithoutSpecification {
/**
* A matcher for any method or constructor that should be substituted.
*/
private final ElementMatcher super MethodDescription> matcher;
/**
* {@code true} if this specification includes virtual invocations.
*/
private final boolean includeVirtualCalls;
/**
* {@code true} if this specification includes {@code super} invocations.
*/
private final boolean includeSuperCalls;
/**
* Creates a new member substitution for a matched method that requires a specification for how to perform a substitution.
*
* @param methodGraphCompiler The method graph compiler to use.
* @param typePoolResolver The type pool resolver to use.
* @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
* @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match.
* @param replacementFactory The replacement factory to use.
* @param matcher A matcher for any method or constructor that should be substituted.
*/
protected ForMatchedMethod(MethodGraph.Compiler methodGraphCompiler,
TypePoolResolver typePoolResolver,
boolean strict,
boolean failIfNoMatch,
Replacement.Factory replacementFactory,
ElementMatcher super MethodDescription> matcher) {
this(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher, true, true);
}
/**
* Creates a new member substitution for a matched method that requires a specification for how to perform a substitution.
*
* @param methodGraphCompiler The method graph compiler to use.
* @param typePoolResolver The type pool resolver to use.
* @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
* @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match.
* @param replacementFactory The replacement factory to use.
* @param matcher A matcher for any method or constructor that should be substituted.
* @param includeVirtualCalls {@code true} if this specification includes virtual invocations.
* @param includeSuperCalls {@code true} if this specification includes {@code super} invocations.
*/
protected ForMatchedMethod(MethodGraph.Compiler methodGraphCompiler,
TypePoolResolver typePoolResolver,
boolean strict,
boolean failIfNoMatch,
Replacement.Factory replacementFactory,
ElementMatcher super MethodDescription> matcher,
boolean includeVirtualCalls,
boolean includeSuperCalls) {
super(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory);
this.matcher = matcher;
this.includeVirtualCalls = includeVirtualCalls;
this.includeSuperCalls = includeSuperCalls;
}
/**
* Limits the substituted method calls to method calls that invoke a method virtually (as opposed to a {@code super} invocation).
*
* @return This specification where only virtual methods are matched if they are not invoked as a virtual call.
*/
public WithoutSpecification onVirtualCall() {
return new ForMatchedMethod(methodGraphCompiler,
typePoolResolver,
strict,
failIfNoMatch,
replacementFactory,
isVirtual().and(matcher),
true,
false);
}
/**
* Limits the substituted method calls to method calls that invoke a method as a {@code super} call.
*
* @return This specification where only virtual methods are matched if they are not invoked as a super call.
*/
public WithoutSpecification onSuperCall() {
return new ForMatchedMethod(methodGraphCompiler,
typePoolResolver,
strict,
failIfNoMatch,
replacementFactory,
isVirtual().and(matcher),
false,
true);
}
/**
* {@inheritDoc}
*/
public MemberSubstitution replaceWith(Substitution.Factory substitutionFactory) {
return new MemberSubstitution(methodGraphCompiler,
typePoolResolver,
strict,
failIfNoMatch,
new Replacement.Factory.Compound(this.replacementFactory, Replacement.ForElementMatchers.Factory.ofMethod(matcher, includeVirtualCalls, includeSuperCalls, substitutionFactory)));
}
}
}
/**
* A type pool resolver is responsible for resolving a {@link TypePool} for locating substituted members.
*/
public interface TypePoolResolver {
/**
* Resolves a type pool to use for locating substituted members.
*
* @param instrumentedType The instrumented type.
* @param instrumentedMethod The instrumented method.
* @param typePool The type pool implicit to the instrumentation.
* @return The type pool to use.
*/
TypePool resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool);
/**
* Returns the implicit type pool.
*/
enum OfImplicitPool implements TypePoolResolver {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public TypePool resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) {
return typePool;
}
}
/**
* A type pool resolver that returns a specific type pool.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForExplicitPool implements TypePoolResolver {
/**
* The type pool to return.
*/
private final TypePool typePool;
/**
* Creates a resolver for an explicit type pool.
*
* @param typePool The type pool to return.
*/
public ForExplicitPool(TypePool typePool) {
this.typePool = typePool;
}
/**
* {@inheritDoc}
*/
public TypePool resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) {
return this.typePool;
}
}
/**
* A type pool resolver that resolves the implicit pool but additionally checks another class file locator.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForClassFileLocator implements TypePoolResolver {
/**
* The class file locator to use.
*/
private final ClassFileLocator classFileLocator;
/**
* The reader mode to apply.
*/
private final TypePool.Default.ReaderMode readerMode;
/**
* Creates a new type pool resolver for a class file locator as a supplement of the implicit type pool.
*
* @param classFileLocator The class file locator to use.
*/
public ForClassFileLocator(ClassFileLocator classFileLocator) {
this(classFileLocator, TypePool.Default.ReaderMode.FAST);
}
/**
* Creates a new type pool resolver for a class file locator as a supplement of the implicit type pool.
*
* @param classFileLocator The class file locator to use.
* @param readerMode The reader mode to apply.
*/
public ForClassFileLocator(ClassFileLocator classFileLocator, TypePool.Default.ReaderMode readerMode) {
this.classFileLocator = classFileLocator;
this.readerMode = readerMode;
}
/**
* Creates a new type pool resolver that supplements the supplied class loader to the implicit type pool.
*
* @param classLoader The class loader to use as a supplement which can be {@code null} to represent the bootstrap loader.
* @return An appropriate type pool resolver.
*/
public static TypePoolResolver of(@MaybeNull ClassLoader classLoader) {
return new ForClassFileLocator(ClassFileLocator.ForClassLoader.of(classLoader));
}
/**
* {@inheritDoc}
*/
public TypePool resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) {
return new TypePool.Default(new TypePool.CacheProvider.Simple(), classFileLocator, readerMode, typePool);
}
}
}
/**
* A substitution replaces or enhances an interaction with a field or method within an instrumented method.
*/
public interface Substitution {
/**
* Resolves this substitution into a stack manipulation.
*
* @param receiver The target type on which a member is accessed.
* @param original The field, method or constructor that is substituted.
* @param parameters All parameters that serve as input to this access.
* @param result The result that is expected from the interaction or {@code void} if no result is expected.
* @param methodHandle A method handle describing the substituted expression.
* @param stackManipulation The original byte code expression that is being executed.
* @param freeOffset The first free offset of the local variable array that can be used for storing values.
* @return A stack manipulation that represents the access.
*/
StackManipulation resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
int freeOffset);
/**
* A factory for creating a substitution for an instrumented method.
*/
interface Factory {
/**
* Creates a substitution for an instrumented method.
*
* @param instrumentedType The instrumented type.
* @param instrumentedMethod The instrumented method.
* @param typePool The type pool being used.
* @return The substitution to apply within the instrumented method.
*/
Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool);
}
/**
* A substitution that drops any field or method access and returns the expected return
* type's default value, i.e {@code null} or zero for primitive types.
*/
enum Stubbing implements Substitution, Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) {
return this;
}
/**
* {@inheritDoc}
*/
public StackManipulation resolve(TypeDescription receiver, ByteCodeElement.Member original, TypeList.Generic parameters, TypeDescription.Generic result, JavaConstant.MethodHandle methodHandle, StackManipulation stackManipulation, int freeOffset) {
List stackManipulations = new ArrayList(parameters.size());
for (int index = parameters.size() - 1; index >= 0; index--) {
stackManipulations.add(Removal.of(parameters.get(index)));
}
return new StackManipulation.Compound(CompoundList.of(stackManipulations, DefaultValue.of(result.asErasure())));
}
}
/**
* A substitution that loads a fixed value.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForValue implements Substitution, Factory {
/**
* The stack manipulation to load the value that represents the substitution.
*/
private final StackManipulation stackManipulation;
/**
* The type of the represented stack manipulation.
*/
private final TypeDescription.Generic typeDescription;
/**
* Creates a new substitution for loading a constant value.
*
* @param stackManipulation The stack manipulation to load the value that represents the substitution.
* @param typeDescription The type of the represented stack manipulation.
*/
public ForValue(StackManipulation stackManipulation, TypeDescription.Generic typeDescription) {
this.stackManipulation = stackManipulation;
this.typeDescription = typeDescription;
}
/**
* {@inheritDoc}
*/
public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) {
return this;
}
/**
* {@inheritDoc}
*/
public StackManipulation resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
int freeOffset) {
List stackManipulations = new ArrayList(parameters.size());
for (int index = parameters.size() - 1; index >= 0; index--) {
stackManipulations.add(Removal.of(parameters.get(index)));
}
if (!typeDescription.asErasure().isAssignableTo(result.asErasure())) {
throw new IllegalStateException("Cannot assign " + typeDescription + " to " + result);
}
return new StackManipulation.Compound(CompoundList.of(stackManipulations, this.stackManipulation));
}
}
/**
* A substitution with a field access.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForFieldAccess implements Substitution {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* A resolver to locate the field to access.
*/
private final FieldResolver fieldResolver;
/**
* Creates a new substitution with a field access.
*
* @param instrumentedType The instrumented type.
* @param fieldResolver A resolver to locate the field to access.
*/
public ForFieldAccess(TypeDescription instrumentedType, FieldResolver fieldResolver) {
this.instrumentedType = instrumentedType;
this.fieldResolver = fieldResolver;
}
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming declaring type for type member.")
public StackManipulation resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
int freeOffset) {
FieldDescription fieldDescription = fieldResolver.resolve(receiver, original, parameters, result);
if (!fieldDescription.isAccessibleTo(instrumentedType)) {
throw new IllegalStateException(instrumentedType + " cannot access " + fieldDescription);
} else if (result.represents(void.class)) {
if (parameters.size() != (fieldDescription.isStatic() ? 1 : 2)) {
throw new IllegalStateException("Cannot set " + fieldDescription + " with " + parameters);
} else if (!fieldDescription.isStatic() && !parameters.get(0).asErasure().isAssignableTo(fieldDescription.getDeclaringType().asErasure())) {
throw new IllegalStateException("Cannot set " + fieldDescription + " on " + parameters.get(0));
} else if (!parameters.get(fieldDescription.isStatic() ? 0 : 1).asErasure().isAssignableTo(fieldDescription.getType().asErasure())) {
throw new IllegalStateException("Cannot set " + fieldDescription + " to " + parameters.get(fieldDescription.isStatic() ? 0 : 1));
}
return FieldAccess.forField(fieldDescription).write();
} else {
if (parameters.size() != (fieldDescription.isStatic() ? 0 : 1)) {
throw new IllegalStateException("Cannot set " + fieldDescription + " with " + parameters);
} else if (!fieldDescription.isStatic() && !parameters.get(0).asErasure().isAssignableTo(fieldDescription.getDeclaringType().asErasure())) {
throw new IllegalStateException("Cannot get " + fieldDescription + " on " + parameters.get(0));
} else if (!fieldDescription.getType().asErasure().isAssignableTo(result.asErasure())) {
throw new IllegalStateException("Cannot get " + fieldDescription + " as " + result);
}
return FieldAccess.forField(fieldDescription).read();
}
}
/**
* A method resolver for locating a field for a substitute.
*/
public interface FieldResolver {
/**
* Resolves the field to substitute with.
*
* @param receiver The target type on which a member is accessed.
* @param original The target field, method or constructor that is substituted,
* @param parameters All parameters that serve as input to this access.
* @param result The result that is expected from the interaction or {@code void} if no result is expected.
* @return The field to substitute with.
*/
FieldDescription resolve(TypeDescription receiver, ByteCodeElement.Member original, TypeList.Generic parameters, TypeDescription.Generic result);
/**
* A simple field resolver that returns a specific field.
*/
@HashCodeAndEqualsPlugin.Enhance
class Simple implements FieldResolver {
/**
* The field to access.
*/
private final FieldDescription fieldDescription;
/**
* Creates a simple field resolver.
*
* @param fieldDescription The field to access.
*/
public Simple(FieldDescription fieldDescription) {
this.fieldDescription = fieldDescription;
}
/**
* {@inheritDoc}
*/
public FieldDescription resolve(TypeDescription receiver, ByteCodeElement.Member original, TypeList.Generic parameters, TypeDescription.Generic result) {
return fieldDescription;
}
}
/**
* A field matcher that resolves a non-static field on the first parameter type of the substituted member usage.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForElementMatcher implements FieldResolver {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The matcher to use for locating the field to substitute with.
*/
private final ElementMatcher super FieldDescription> matcher;
/**
* Creates a new field resolver that locates a field on the receiver type using a matcher.
*
* @param instrumentedType The instrumented type.
* @param matcher The matcher to use for locating the field to substitute with.
*/
protected ForElementMatcher(TypeDescription instrumentedType, ElementMatcher super FieldDescription> matcher) {
this.instrumentedType = instrumentedType;
this.matcher = matcher;
}
/**
* {@inheritDoc}
*/
public FieldDescription resolve(TypeDescription receiver, ByteCodeElement.Member original, TypeList.Generic parameters, TypeDescription.Generic result) {
if (parameters.isEmpty()) {
throw new IllegalStateException("Cannot substitute parameterless instruction with " + original);
} else if (parameters.get(0).isPrimitive() || parameters.get(0).isArray()) {
throw new IllegalStateException("Cannot access field on primitive or array type for " + original);
}
TypeDefinition current = parameters.get(0).accept(new TypeDescription.Generic.Visitor.Substitutor.ForReplacement(instrumentedType));
do {
FieldList> fields = current.getDeclaredFields().filter(not(isStatic()).and(isVisibleTo(instrumentedType)).and(matcher));
if (fields.size() == 1) {
return fields.getOnly();
} else if (fields.size() > 1) {
throw new IllegalStateException("Ambiguous field location of " + fields);
}
current = current.getSuperClass();
} while (current != null);
throw new IllegalStateException("Cannot locate field matching " + matcher + " on " + receiver);
}
}
}
/**
* A factory for a substitution that substitutes with a given field.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class OfGivenField implements Factory {
/**
* The field to substitute with.
*/
private final FieldDescription fieldDescription;
/**
* Creates a new factory that substitues with a given field.
*
* @param fieldDescription The field to substitute with.
*/
public OfGivenField(FieldDescription fieldDescription) {
this.fieldDescription = fieldDescription;
}
/**
* {@inheritDoc}
*/
public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) {
return new ForFieldAccess(instrumentedType, new FieldResolver.Simple(fieldDescription));
}
}
/**
* A factory for a substitution that locates a field on the receiver type using a matcher.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class OfMatchedField implements Factory {
/**
* The matcher to apply.
*/
private final ElementMatcher super FieldDescription> matcher;
/**
* Creates a new substitution factory that locates a field by applying a matcher on the receiver type.
*
* @param matcher The matcher to apply.
*/
public OfMatchedField(ElementMatcher super FieldDescription> matcher) {
this.matcher = matcher;
}
/**
* {@inheritDoc}
*/
public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) {
return new ForFieldAccess(instrumentedType, new FieldResolver.ForElementMatcher(instrumentedType, matcher));
}
}
}
/**
* A substitution with a method invocation.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForMethodInvocation implements Substitution {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The method resolver to use.
*/
private final MethodResolver methodResolver;
/**
* Creates a new method-resolving substitution.
*
* @param instrumentedType The instrumented type.
* @param methodResolver The method resolver to use.
*/
public ForMethodInvocation(TypeDescription instrumentedType, MethodResolver methodResolver) {
this.instrumentedType = instrumentedType;
this.methodResolver = methodResolver;
}
/**
* {@inheritDoc}
*/
public StackManipulation resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
int freeOffset) {
MethodDescription methodDescription = methodResolver.resolve(receiver, original, parameters, result);
if (!methodDescription.isAccessibleTo(instrumentedType)) {
throw new IllegalStateException(instrumentedType + " cannot access " + methodDescription);
}
TypeList.Generic mapped = methodDescription.isStatic()
? methodDescription.getParameters().asTypeList()
: new TypeList.Generic.Explicit(CompoundList.of(methodDescription.getDeclaringType(), methodDescription.getParameters().asTypeList()));
if (!methodDescription.getReturnType().asErasure().isAssignableTo(result.asErasure())) {
throw new IllegalStateException("Cannot assign return value of " + methodDescription + " to " + result);
} else if (mapped.size() != parameters.size()) {
throw new IllegalStateException("Cannot invoke " + methodDescription + " on " + parameters.size() + " parameters");
}
for (int index = 0; index < mapped.size(); index++) {
if (!parameters.get(index).asErasure().isAssignableTo(mapped.get(index).asErasure())) {
throw new IllegalStateException("Cannot invoke " + methodDescription + " on parameter " + index + " of type " + parameters.get(index));
}
}
return methodDescription.isVirtual() ? MethodInvocation.invoke(methodDescription).virtual(mapped.get(THIS_REFERENCE).asErasure()) : MethodInvocation.invoke(methodDescription);
}
/**
* A method resolver for locating a method for a substitute.
*/
public interface MethodResolver {
/**
* Resolves the method to substitute with.
*
* @param receiver The target type on which a member is accessed.
* @param original The target field, method or constructor that is substituted,
* @param parameters All parameters that serve as input to this access.
* @param result The result that is expected from the interaction or {@code void} if no result is expected.
* @return The field to substitute with.
*/
MethodDescription resolve(TypeDescription receiver, ByteCodeElement.Member original, TypeList.Generic parameters, TypeDescription.Generic result);
/**
* A simple method resolver that returns a given method.
*/
@HashCodeAndEqualsPlugin.Enhance
class Simple implements MethodResolver {
/**
* The method to substitute with.
*/
private final MethodDescription methodDescription;
/**
* Creates a new simple method resolver.
*
* @param methodDescription The method to substitute with.
*/
public Simple(MethodDescription methodDescription) {
this.methodDescription = methodDescription;
}
/**
* {@inheritDoc}
*/
public MethodDescription resolve(TypeDescription receiver, ByteCodeElement.Member original, TypeList.Generic parameters, TypeDescription.Generic result) {
return methodDescription;
}
}
/**
* A method resolver that locates a non-static method by locating it from the receiver type.
*/
@HashCodeAndEqualsPlugin.Enhance
class Matching implements MethodResolver {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The method graph compiler to use.
*/
private final MethodGraph.Compiler methodGraphCompiler;
/**
* The matcher to use for locating the method to substitute with.
*/
private final ElementMatcher super MethodDescription> matcher;
/**
* Creates a new matching method resolver.
*
* @param instrumentedType The instrumented type.
* @param methodGraphCompiler The method graph compiler to use.
* @param matcher The matcher to use for locating the method to substitute with.
*/
public Matching(TypeDescription instrumentedType, MethodGraph.Compiler methodGraphCompiler, ElementMatcher super MethodDescription> matcher) {
this.instrumentedType = instrumentedType;
this.methodGraphCompiler = methodGraphCompiler;
this.matcher = matcher;
}
/**
* {@inheritDoc}
*/
public MethodDescription resolve(TypeDescription receiver, ByteCodeElement.Member original, TypeList.Generic parameters, TypeDescription.Generic result) {
if (parameters.isEmpty()) {
throw new IllegalStateException("Cannot substitute parameterless instruction with " + original);
} else if (parameters.get(0).isPrimitive() || parameters.get(0).isArray()) {
throw new IllegalStateException("Cannot invoke method on primitive or array type for " + original);
}
TypeDefinition typeDefinition = parameters.get(0).accept(new TypeDescription.Generic.Visitor.Substitutor.ForReplacement(instrumentedType));
List candidates = CompoundList.of(methodGraphCompiler.compile(typeDefinition, instrumentedType).listNodes()
.asMethodList()
.filter(matcher), typeDefinition.getDeclaredMethods().filter(isPrivate().and(isVisibleTo(instrumentedType)).and(matcher)));
if (candidates.size() == 1) {
return candidates.get(0);
} else {
throw new IllegalStateException("Not exactly one method that matches " + matcher + ": " + candidates);
}
}
}
}
/**
* A factory for a substitution that invokes the instrumented method.
*/
enum OfInstrumentedMethod implements Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) {
return new ForMethodInvocation(instrumentedType, new MethodResolver.Simple(instrumentedMethod));
}
}
/**
* A factory for a substitution that invokes a given method.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class OfGivenMethod implements Factory {
/**
* The method to invoke.
*/
private final MethodDescription methodDescription;
/**
* Creates a new factory for a substitution that invokes a given method.
*
* @param methodDescription The method to invoke.
*/
public OfGivenMethod(MethodDescription methodDescription) {
this.methodDescription = methodDescription;
}
/**
* {@inheritDoc}
*/
public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) {
return new ForMethodInvocation(instrumentedType, new MethodResolver.Simple(methodDescription));
}
}
/**
* A factory for a substitution that locates a method on the receiver type using a matcher.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class OfMatchedMethod implements Factory {
/**
* The matcher for locating the method to substitute with.
*/
private final ElementMatcher super MethodDescription> matcher;
/**
* The method graph compiler to use.
*/
private final MethodGraph.Compiler methodGraphCompiler;
/**
* Creates a factory for a substitution that locates a method on the receiver type.
*
* @param matcher The matcher for locating the method to substitute with.
* @param methodGraphCompiler The method graph compiler to use.
*/
public OfMatchedMethod(ElementMatcher super MethodDescription> matcher, MethodGraph.Compiler methodGraphCompiler) {
this.matcher = matcher;
this.methodGraphCompiler = methodGraphCompiler;
}
/**
* {@inheritDoc}
*/
public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) {
return new ForMethodInvocation(instrumentedType, new MethodResolver.Matching(instrumentedType, methodGraphCompiler, matcher));
}
}
}
/**
* A substitution chain allows for chaining multiple substitution steps for a byte code element being replaced.
*/
@HashCodeAndEqualsPlugin.Enhance
class Chain implements Substitution {
/**
* The assigner to use.
*/
private final Assigner assigner;
/**
* The typing of the assignment to use.
*/
private final Assigner.Typing typing;
/**
* The substitution steps to apply.
*/
private final List steps;
/**
* Creates a new substitution chain.
*
* @param assigner The assigner to use.
* @param typing The typing of the assignment to use.
* @param steps The substitution steps to apply.
*/
protected Chain(Assigner assigner, Assigner.Typing typing, List steps) {
this.assigner = assigner;
this.typing = typing;
this.steps = steps;
}
/**
* Creates a new substitution chain that uses a default assigner and static typing.
*
* @return A new substitution chain.
*/
public static Chain.Factory withDefaultAssigner() {
return with(Assigner.DEFAULT, Assigner.Typing.STATIC);
}
/**
* Creates a new substitution chain.
*
* @param assigner The assigner to use.
* @param typing The typing of the assignment to use.
* @return A new substitution chain.
*/
public static Chain.Factory with(Assigner assigner, Assigner.Typing typing) {
return new Chain.Factory(assigner, typing, Collections.emptyList());
}
/**
* {@inheritDoc}
*/
public StackManipulation resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
int freeOffset) {
List stackManipulations = new ArrayList(1
+ parameters.size() + steps.size() * 2
+ (result.represents(void.class) ? 0 : 2));
Map offsets = new HashMap();
for (int index = parameters.size() - 1; index >= 0; index--) {
stackManipulations.add(MethodVariableAccess.of(parameters.get(index)).storeAt(freeOffset));
offsets.put(index, freeOffset);
freeOffset += parameters.get(index).getStackSize().getSize();
}
stackManipulations.add(DefaultValue.of(result));
TypeDescription.Generic current = result;
for (Step step : steps) {
Step.Resolution resolution = step.resolve(receiver, original, parameters, result, methodHandle, stackManipulation, current, offsets, freeOffset);
stackManipulations.add(resolution.getStackManipulation());
current = resolution.getResultType();
}
StackManipulation assignment = assigner.assign(current, result, typing);
if (!assignment.isValid()) {
throw new IllegalStateException("Failed to assign " + current + " to " + result);
}
stackManipulations.add(assignment);
return new StackManipulation.Compound(stackManipulations);
}
/**
* Represents a step of a substitution chain.
*/
public interface Step {
/**
* Resolves this step of a substitution chain.
*
* @param receiver The target result type of the substitution.
* @param original The byte code element that is currently substituted.
* @param parameters The parameters of the substituted element.
* @param result The resulting type of the substituted element.
* @param methodHandle A method handle of the stackManipulation invocation that is being substituted.
* @param stackManipulation The byte code instruction that is being substituted.
* @param current The current type of the applied substitution that is the top element on the operand stack.
* @param offsets The arguments of the substituted byte code element mapped to their local variable offsets.
* @param freeOffset The first free offset in the local variable array.
* @return A resolved substitution step for the supplied inputs.
*/
Resolution resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
TypeDescription.Generic current,
Map offsets,
int freeOffset);
/**
* A resolved substitution step.
*/
interface Resolution {
/**
* Returns the stack manipulation to apply the substitution.
*
* @return The stack manipulation to apply the substitution.
*/
StackManipulation getStackManipulation();
/**
* Returns the resulting type of the substitution or {@code void} if no resulting value is applied.
*
* @return The resulting type of the substitution or {@code void} if no resulting value is applied.
*/
TypeDescription.Generic getResultType();
}
/**
* Resolves a substitution for an instrumented method.
*/
interface Factory {
/**
* Creates a substitution step for an instrumented method.
*
* @param assigner The assigner to use.
* @param typing The typing to use.
* @param instrumentedType The instrumented type.
* @param instrumentedMethod The instrumented method.
* @return The substitution step to apply.
*/
Step make(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod);
}
/**
* A step that executes the original method invocation or field access.
*/
enum OfOriginalExpression implements Step, Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Resolution resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
TypeDescription.Generic current,
Map offsets,
int freeOffset) {
List stackManipulations;
if (original instanceof MethodDescription && ((MethodDescription) original).isConstructor()) {
stackManipulations = new ArrayList(parameters.size() + 4);
stackManipulations.add(Removal.of(current));
stackManipulations.add(TypeCreation.of(original.getDeclaringType().asErasure()));
stackManipulations.add(Duplication.SINGLE);
} else {
stackManipulations = new ArrayList(parameters.size() + 4);
stackManipulations.add(Removal.of(current));
}
for (int index = 0; index < parameters.size(); index++) {
stackManipulations.add(MethodVariableAccess.of(parameters.get(index)).loadFrom(offsets.get(index)));
}
if (original instanceof MethodDescription) {
stackManipulations.add(stackManipulation);
return new Simple(new StackManipulation.Compound(stackManipulations), ((MethodDescription) original).isConstructor()
? original.getDeclaringType().asGenericType()
: ((MethodDescription) original).getReturnType());
} else if (original instanceof FieldDescription) {
if (original.isStatic()) {
if (parameters.isEmpty()) {
stackManipulations.add(stackManipulation);
return new Simple(new StackManipulation.Compound(stackManipulations), ((FieldDescription) original).getType());
} else /* if (parameters.size() == 1) */ {
stackManipulations.add(stackManipulation);
return new Simple(new StackManipulation.Compound(stackManipulations), TypeDefinition.Sort.describe(void.class));
}
} else {
if (parameters.size() == 1) {
stackManipulations.add(FieldAccess.forField((FieldDescription) original).read());
return new Simple(new StackManipulation.Compound(stackManipulations), ((FieldDescription) original).getType());
} else /* if (parameters.size() == 2) */ {
stackManipulations.add(FieldAccess.forField((FieldDescription) original).write());
return new Simple(new StackManipulation.Compound(stackManipulations), TypeDefinition.Sort.describe(void.class));
}
}
} else {
throw new IllegalArgumentException("Unexpected target type: " + original);
}
}
/**
* {@inheritDoc}
*/
public Step make(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return this;
}
}
/**
* A simple substitution step within a substitution chain.
*/
@HashCodeAndEqualsPlugin.Enhance
class Simple implements Step, Resolution, Factory {
/**
* The stack manipulation to apply.
*/
private final StackManipulation stackManipulation;
/**
* The resulting type of applying the stack manipulation.
*/
private final TypeDescription.Generic resultType;
/**
* Creates a new simple substitution step.
*
* @param stackManipulation The stack manipulation to apply.
* @param resultType The resulting type of applying the stack manipulation.
*/
public Simple(StackManipulation stackManipulation, Type resultType) {
this(stackManipulation, TypeDefinition.Sort.describe(resultType));
}
/**
* Creates a new simple substitution step.
*
* @param stackManipulation The stack manipulation to apply.
* @param resultType The resulting type of applying the stack manipulation.
*/
public Simple(StackManipulation stackManipulation, TypeDescription.Generic resultType) {
this.stackManipulation = stackManipulation;
this.resultType = resultType;
}
/**
* Resolves a compile-time constant as the next step value.
*
* @param value The compile-time constant to resolve.
* @return An appropriate step factory.
*/
public static Step.Factory of(Object value) {
ConstantValue constant = ConstantValue.Simple.wrap(value);
return new Simple(constant.toStackManipulation(), constant.getTypeDescription().asGenericType());
}
/**
* {@inheritDoc}
*/
public Step make(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return this;
}
/**
* {@inheritDoc}
*/
public Resolution resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
TypeDescription.Generic current,
Map offsets,
int freeOffset) {
return receiver.represents(void.class)
? this
: new Simple(new StackManipulation.Compound(Removal.of(current), this.stackManipulation), resultType);
}
/**
* {@inheritDoc}
*/
public StackManipulation getStackManipulation() {
return stackManipulation;
}
/**
* {@inheritDoc}
*/
public TypeDescription.Generic getResultType() {
return resultType;
}
}
/**
* A step within a substitution chain that converts the current type to the expected return type.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForAssignment implements Step {
/**
* The result type or {@code null} if the type of the substitution result should be targeted.
*/
@MaybeNull
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
private final TypeDescription.Generic result;
/**
* The assigner to use.
*/
private final Assigner assigner;
/**
* Creates a step for a type assignment.
*
* @param result The result type or {@code null} if the type of the substitution result should be targeted.
* @param assigner The assigner to use.
*/
protected ForAssignment(@MaybeNull TypeDescription.Generic result, Assigner assigner) {
this.result = result;
this.assigner = assigner;
}
/**
* Creates a step factory that casts the current stack top value to the specified type.
*
* @param type The type that should be cast to.
* @return An appropriate step factory.
*/
public static Step.Factory castTo(Type type) {
return new Factory(TypeDefinition.Sort.describe(type));
}
/**
* Creates a step factory that casts the current stack top value to the specified type.
*
* @param typeDescription The description of the type that should be cast to.
* @return An appropriate step factory.
*/
public static Step.Factory castTo(TypeDescription.Generic typeDescription) {
return new Factory(typeDescription);
}
/**
* Creates a step factory that casts the current stack top value to the expected return value.
*
* @return An appropriate step factory.
*/
public static Step.Factory castToSubstitutionResult() {
return new Factory(null);
}
/**
* {@inheritDoc}
*/
public Resolution resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
TypeDescription.Generic current,
Map offsets,
int freeOffset) {
StackManipulation assignment = assigner.assign(current, this.result == null ? result : this.result, Assigner.Typing.DYNAMIC);
if (!assignment.isValid()) {
throw new IllegalStateException("Failed to assign " + current + " to " + (this.result == null ? result : this.result));
}
return new Simple(assignment, this.result == null ? result : this.result);
}
/**
* A factory for creating a step for a dynamic type assignment.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Factory implements Step.Factory {
/**
* The result type or {@code null} if the type of the substitution result should be targeted.
*/
@MaybeNull
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
private final TypeDescription.Generic result;
/**
* Creates a new factory for a step that applies a type assignment.
*
* @param result The result type or {@code null} if the type of the substitution result should be targeted.
*/
protected Factory(@MaybeNull TypeDescription.Generic result) {
this.result = result;
}
/**
* {@inheritDoc}
*/
public Step make(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new ForAssignment(result, assigner);
}
}
}
/**
* A step that substitutes an argument of a given index with a compatible type.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForArgumentSubstitution implements Step {
/**
* The stack manipulation that loads the substituted argument.
*/
private final StackManipulation substitution;
/**
* The type of the substituted argument.
*/
private final TypeDescription.Generic typeDescription;
/**
* The index of the argument to substitute.
*/
private final int index;
/**
* The assigner to use for assigning the argument.
*/
private final Assigner assigner;
/**
* The typing to use for the argument assignment.
*/
private final Assigner.Typing typing;
/**
* Creates an argument substitution step.
*
* @param substitution The stack manipulation that loads the substituted argument.
* @param typeDescription The type of the substituted argument.
* @param index The index of the argument to substitute.
* @param assigner The assigner to use for assigning the argument.
* @param typing The typing to use for the argument assignment.
*/
protected ForArgumentSubstitution(StackManipulation substitution, TypeDescription.Generic typeDescription, int index, Assigner assigner, Assigner.Typing typing) {
this.substitution = substitution;
this.typeDescription = typeDescription;
this.index = index;
this.assigner = assigner;
this.typing = typing;
}
/**
* Resolves a step substitution factory for a compile-time constant to replace an argument value at a given index.
*
* @param value The compile-time constant to replace.
* @param index The index of the substituted argument.
* @return An appropriate step factory.
*/
public static Step.Factory of(Object value, int index) {
if (index < 0) {
throw new IllegalArgumentException("Index cannot be negative: " + index);
}
ConstantValue constant = ConstantValue.Simple.wrap(value);
return new Factory(constant.toStackManipulation(), constant.getTypeDescription().asGenericType(), index);
}
/**
* {@inheritDoc}
*/
public Resolution resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
TypeDescription.Generic current,
Map offsets,
int freeOffset) {
if (index >= parameters.size()) {
throw new IllegalStateException(original + " has not " + index + " arguments");
}
StackManipulation assignment = assigner.assign(typeDescription, parameters.get(index), typing);
if (!assignment.isValid()) {
throw new IllegalStateException("Cannot assign " + typeDescription + " to " + parameters.get(index));
}
return new Simple(new StackManipulation.Compound(substitution, assignment, MethodVariableAccess.of(parameters.get(index)).storeAt(offsets.get(index))), current);
}
/**
* A factory to create an argument substitution step.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Factory implements Step.Factory {
/**
* The stack manipulation that loads the substituted argument.
*/
private final StackManipulation stackManipulation;
/**
* The type of the substituted argument.
*/
private final TypeDescription.Generic typeDescription;
/**
* The index of the argument to substitute.
*/
private final int index;
/**
* Creates a factory for an argument substitution step.
*
* @param stackManipulation The stack manipulation that loads the substituted argument.
* @param type The type of the substituted argument.
* @param index The index of the argument to substitute.
*/
public Factory(StackManipulation stackManipulation, Type type, int index) {
this(stackManipulation, TypeDefinition.Sort.describe(type), index);
}
/**
* Creates a factory for an argument substitution step.
*
* @param stackManipulation The stack manipulation that loads the substituted argument.
* @param typeDescription The type of the substituted argument.
* @param index The index of the argument to substitute.
*/
public Factory(StackManipulation stackManipulation, TypeDescription.Generic typeDescription, int index) {
this.stackManipulation = stackManipulation;
this.typeDescription = typeDescription;
this.index = index;
}
/**
* {@inheritDoc}
*/
public Step make(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new ForArgumentSubstitution(stackManipulation, typeDescription, index, assigner, typing);
}
}
}
/**
* A step that loads an argument to a method as the current chain value.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForArgumentLoading implements Step, Factory {
/**
* The index of the argument to substitute.
*/
private final int index;
/**
* Creates an argument loading step.
*
* @param index The index of the argument to load.
*/
protected ForArgumentLoading(int index) {
this.index = index;
}
/**
* {@inheritDoc}
*/
public Step make(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return this;
}
/**
* {@inheritDoc}
*/
public Resolution resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
TypeDescription.Generic current,
Map offsets,
int freeOffset) {
if (index >= parameters.size()) {
throw new IllegalStateException(original + " has not " + index + " arguments");
}
return new Simple(new StackManipulation.Compound(Removal.of(current), MethodVariableAccess.of(parameters.get(index)).loadFrom(offsets.get(index))), parameters.get(index));
}
}
/**
* Creates a step for a field access.
*/
@HashCodeAndEqualsPlugin.Enhance
abstract class ForField implements Step {
/**
* The field description accessed in this step.
*/
protected final FieldDescription fieldDescription;
/**
* The assigner to use.
*/
protected final Assigner assigner;
/**
* The typing to use when assigning.
*/
protected final Assigner.Typing typing;
/**
* Creates a new step for a field access.
*
* @param fieldDescription The field description accessed in this step.
* @param assigner The assigner to use.
* @param typing The typing to use when assigning.
*/
protected ForField(FieldDescription fieldDescription, Assigner assigner, Assigner.Typing typing) {
this.fieldDescription = fieldDescription;
this.assigner = assigner;
this.typing = typing;
}
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Field description always has declaring type.")
public Resolution resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
TypeDescription.Generic current,
Map offsets,
int freeOffset) {
List stackManipulations = new ArrayList(2);
if (fieldDescription.isStatic()) {
stackManipulations.add(Removal.of(current));
} else {
StackManipulation assignment = assigner.assign(current, fieldDescription.getDeclaringType().asGenericType(), typing);
if (!assignment.isValid()) {
throw new IllegalStateException("Cannot assign " + current + " to " + fieldDescription.getDeclaringType());
}
stackManipulations.add(assignment);
}
return doResolve(original, parameters, offsets, new StackManipulation.Compound(stackManipulations));
}
/**
* Completes the resolution.
*
* @param original The byte code element that is currently substituted.
* @param parameters The parameters of the substituted element.
* @param offsets The arguments of the substituted byte code element mapped to their local variable offsets.
* @param stackManipulation A stack manipulation to prepare the field access.
* @return A resolved substitution step for the supplied inputs.
*/
protected abstract Resolution doResolve(ByteCodeElement.Member original, TypeList.Generic parameters, Map offsets, StackManipulation stackManipulation);
/**
* A step for reading a field.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Read extends ForField {
/**
* Creates a step for reading a field.
*
* @param fieldDescription A description of the field being read.
* @param assigner The assigner to use.
* @param typing The typing to use when assigning.
*/
protected Read(FieldDescription fieldDescription, Assigner assigner, Assigner.Typing typing) {
super(fieldDescription, assigner, typing);
}
/**
* {@inheritDoc}
*/
protected Resolution doResolve(ByteCodeElement.Member original, TypeList.Generic parameters, Map offsets, StackManipulation stackManipulation) {
return new Simple(new StackManipulation.Compound(stackManipulation, FieldAccess.forField(fieldDescription).read()), fieldDescription.getType());
}
/**
* A factory for creating a field read step in a chain.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Factory implements Step.Factory {
/**
* A description of the field being read.
*/
private final FieldDescription fieldDescription;
/**
* Creates a factory for a step reading a field.
*
* @param field The field being read.
*/
public Factory(Field field) {
this(new FieldDescription.ForLoadedField(field));
}
/**
* Creates a factory for a step reading a field.
*
* @param fieldDescription A description of the field being read.
*/
public Factory(FieldDescription fieldDescription) {
this.fieldDescription = fieldDescription;
}
/**
* {@inheritDoc}
*/
public Step make(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new Read(fieldDescription, assigner, typing);
}
}
}
/**
* A step for writing to a field.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Write extends ForField {
/**
* The index of the parameter being accessed. If the targeted element is a non-static method, is increased by one.
*/
private final int index;
/**
* Creates a step for writing to a field.
*
* @param fieldDescription A description of the field to write to.
* @param assigner The assigner to use.
* @param typing The typing to use when assigning.
* @param index The index of the parameter being accessed. If the targeted element is a non-static method, is increased by one.
*/
protected Write(FieldDescription fieldDescription, Assigner assigner, Assigner.Typing typing, int index) {
super(fieldDescription, assigner, typing);
this.index = index;
}
/**
* {@inheritDoc}
*/
protected Resolution doResolve(ByteCodeElement.Member original, TypeList.Generic parameters, Map offsets, StackManipulation stackManipulation) {
int index = ((original.getModifiers() & Opcodes.ACC_STATIC) == 0)
&& !(original instanceof MethodDescription
&& ((MethodDescription) original).isConstructor()) ? this.index + 1 : this.index;
if (index >= parameters.size()) {
throw new IllegalStateException(original + " does not define an argument with index " + index);
}
StackManipulation assignment = assigner.assign(parameters.get(index), fieldDescription.getType(), typing);
if (!assignment.isValid()) {
throw new IllegalStateException("Cannot write " + parameters.get(index) + " to " + fieldDescription);
}
return new Simple(new StackManipulation.Compound(stackManipulation,
MethodVariableAccess.of(parameters.get(index)).loadFrom(offsets.get(index)),
assignment,
FieldAccess.forField(fieldDescription).write()), TypeDefinition.Sort.describe(void.class));
}
/**
* A factory for creating a step to write to a field.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Factory implements Step.Factory {
/**
* A description of the field to write to.
*/
private final FieldDescription fieldDescription;
/**
* The index of the parameter being accessed. If the targeted element is a non-static method, is increased by one.
*/
private final int index;
/**
* Creates a factory for writing to a field.
*
* @param field The field to write to.
* @param index The index of the parameter being accessed. If the targeted element is a non-static method, is increased by one.
*/
public Factory(Field field, int index) {
this(new FieldDescription.ForLoadedField(field), index);
}
/**
* Creates a factory for writing to a field.
*
* @param fieldDescription A description of the field to write to.
* @param index The index of the parameter being accessed. If the targeted element is a non-static method, is increased by one.
*/
public Factory(FieldDescription fieldDescription, int index) {
this.fieldDescription = fieldDescription;
this.index = index;
}
/**
* {@inheritDoc}
*/
public Step make(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new Write(fieldDescription, assigner, typing, index);
}
}
}
}
/**
* A step for invoking a method or constructor. If non-static, a method is invoked upon a the current stack argument of the chain.
* Arguments are loaded from the intercepted byte code element with a possibility of substitution.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForInvocation implements Step {
/**
* The invoked method or constructor.
*/
private final MethodDescription methodDescription;
/**
* A mapping of substituted parameter indices. For targets that are non-static methods, the targeted index is increased by one.
*/
private final Map substitutions;
/**
* The assigner to use.
*/
private final Assigner assigner;
/**
* The typing to use when assigning.
*/
private final Assigner.Typing typing;
/**
* Creates a new step of an invocation.
*
* @param methodDescription The invoked method or constructor.
* @param substitutions A mapping of substituted parameter indices. For targets that are non-static methods, the targeted index is increased by one.
* @param assigner The assigner to use.
* @param typing The typing to use when assigning.
*/
protected ForInvocation(MethodDescription methodDescription, Map substitutions, Assigner assigner, Assigner.Typing typing) {
this.methodDescription = methodDescription;
this.substitutions = substitutions;
this.assigner = assigner;
this.typing = typing;
}
/**
* {@inheritDoc}
*/
public Resolution resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
TypeDescription.Generic current,
Map offsets,
int freeOffset) {
List stackManipulations = new ArrayList(3 + parameters.size() * 2);
if (methodDescription.isStatic()) {
stackManipulations.add(Removal.of(current));
} else if (methodDescription.isConstructor()) {
stackManipulations.add(Removal.of(current));
stackManipulations.add(TypeCreation.of(methodDescription.getDeclaringType().asErasure()));
} else {
StackManipulation assignment = assigner.assign(current, methodDescription.getDeclaringType().asGenericType(), typing);
if (!assignment.isValid()) {
throw new IllegalStateException("Cannot assign " + current + " to " + methodDescription.getDeclaringType());
}
stackManipulations.add(assignment);
}
boolean shift = ((original.getModifiers() & Opcodes.ACC_STATIC) == 0) && !(original instanceof MethodDescription && ((MethodDescription) original).isConstructor());
for (int index = 0; index < methodDescription.getParameters().size(); index++) {
int substitution = substitutions.containsKey(index + (shift ? 1 : 0)) ? substitutions.get(index + (shift ? 1 : 0)) : index + (shift ? 1 : 0);
if (substitution >= parameters.size()) {
throw new IllegalStateException(original + " does not support an index " + substitution);
}
stackManipulations.add(MethodVariableAccess.of(parameters.get(substitution)).loadFrom(offsets.get(substitution)));
StackManipulation assignment = assigner.assign(parameters.get(substitution), methodDescription.getParameters().get(index).getType(), typing);
if (!assignment.isValid()) {
throw new IllegalStateException("Cannot assign parameter with " + index + " of type " + parameters.get(substitution) + " to " + methodDescription);
}
stackManipulations.add(assignment);
}
stackManipulations.add(MethodInvocation.invoke(methodDescription));
return new Simple(new StackManipulation.Compound(stackManipulations), methodDescription.getReturnType());
}
/**
* A factory to create a step for a method invocation.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Factory implements Step.Factory {
/**
* The invoked method or constructor.
*/
private final MethodDescription methodDescription;
/**
* A mapping of substituted parameter indices. For targets that are non-static methods, the targeted index is increased by one.
*/
private final Map substitutions;
/**
* Creates a factory for a method invocation without parameter substitutions.
*
* @param method The invoked method.
*/
public Factory(Method method) {
this(new MethodDescription.ForLoadedMethod(method));
}
/**
* Creates a factory for a method invocation without parameter substitutions.
*
* @param constructor The constructor.
*/
public Factory(Constructor> constructor) {
this(new MethodDescription.ForLoadedConstructor(constructor));
}
/**
* Creates a factory for a method invocation without parameter substitutions.
*
* @param methodDescription The invoked method or constructor.
*/
public Factory(MethodDescription methodDescription) {
this(methodDescription, Collections.emptyMap());
}
/**
* Creates a factory for a method invocation.
*
* @param methodDescription The invoked method or constructor.
* @param substitutions A mapping of substituted parameter indices. For targets that are non-static methods,
* the targeted index is increased by one.
*/
public Factory(MethodDescription methodDescription, Map substitutions) {
this.methodDescription = methodDescription;
this.substitutions = substitutions;
}
/**
* {@inheritDoc}
*/
public Step make(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new ForInvocation(methodDescription, substitutions, assigner, typing);
}
}
}
/**
* A step that invokes a delegation method based on annotations on the parameters of the targeted method.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForDelegation implements Step {
/**
* The type on top of the stack after the delegation is complete.
*/
private final TypeDescription.Generic returned;
/**
* The dispatcher to use.
*/
private final Dispatcher.Resolved dispatcher;
/**
* A list of offset mappings to execute prior to delegation.
*/
private final List offsetMappings;
/**
* @param returned The type on top of the stack after the delegation is complete.
* @param dispatcher The dispatcher to use.
* @param offsetMappings A list of offset mappings to execute prior to delegation.
*/
protected ForDelegation(TypeDescription.Generic returned, Dispatcher.Resolved dispatcher, List offsetMappings) {
this.returned = returned;
this.dispatcher = dispatcher;
this.offsetMappings = offsetMappings;
}
/**
* Returns a delegating step factory for the supplied method.
*
* @param method The method to delegate to.
* @return An appropriate step factory.
*/
public static Step.Factory to(Method method) {
return to(new MethodDescription.ForLoadedMethod(method));
}
/**
* Returns a delegating step factory for the supplied constructor.
*
* @param constructor The constructor to delegate to.
* @return An appropriate step factory.
*/
public static Step.Factory to(Constructor> constructor) {
return to(new MethodDescription.ForLoadedConstructor(constructor));
}
/**
* Returns a delegating step factory for the supplied method description..
*
* @param methodDescription A description of the method or constructor to delegate to.
* @return An appropriate step factory.
*/
public static Step.Factory to(MethodDescription.InDefinedShape methodDescription) {
if (methodDescription.isTypeInitializer()) {
throw new IllegalArgumentException("Cannot delegate to a type initializer: " + methodDescription);
}
return to(methodDescription, Dispatcher.ForRegularInvocation.Factory.INSTANCE, Collections.>emptyList());
}
/**
* Creates an appropriate step factory for the given delegate method, dispatcher factory and user factories.
*
* @param delegate A description of the method or constructor to delegate to.
* @param dispatcherFactory The dispatcher factory to use.
* @param userFactories Factories for custom annotation bindings.
* @return An appropriate step factory.
*/
@SuppressWarnings("unchecked")
private static Step.Factory to(MethodDescription.InDefinedShape delegate, Dispatcher.Factory dispatcherFactory, List extends OffsetMapping.Factory>> userFactories) {
if (delegate.isTypeInitializer()) {
throw new IllegalArgumentException("Cannot delegate to type initializer: " + delegate);
}
return new Factory(delegate, dispatcherFactory.make(delegate), CompoundList.of(Arrays.asList(
OffsetMapping.ForArgument.Factory.INSTANCE,
OffsetMapping.ForThisReference.Factory.INSTANCE,
OffsetMapping.ForAllArguments.Factory.INSTANCE,
OffsetMapping.ForSelfCallHandle.Factory.INSTANCE,
OffsetMapping.ForField.Unresolved.Factory.INSTANCE,
OffsetMapping.ForFieldHandle.Unresolved.GetterFactory.INSTANCE,
OffsetMapping.ForFieldHandle.Unresolved.SetterFactory.INSTANCE,
OffsetMapping.ForOrigin.Factory.INSTANCE,
OffsetMapping.ForStubValue.Factory.INSTANCE,
new OffsetMapping.ForStackManipulation.OfDefaultValue(Unused.class),
OffsetMapping.ForCurrent.Factory.INSTANCE), userFactories));
}
/**
* Returns a builder for creating a {@link ForDelegation} with custom configuration.
*
* @return A bulder for creating a custom delegator.
*/
public static WithCustomMapping withCustomMapping() {
return new WithCustomMapping(Dispatcher.ForRegularInvocation.Factory.INSTANCE, Collections., OffsetMapping.Factory>>emptyMap());
}
/**
* {@inheritDoc}
*/
public Resolution resolve(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
JavaConstant.MethodHandle methodHandle,
StackManipulation stackManipulation,
TypeDescription.Generic current,
Map offsets,
int freeOffset) {
List stackManipulations = new ArrayList(offsetMappings.size() + 3);
stackManipulations.add(current.represents(void.class)
? StackManipulation.Trivial.INSTANCE
: MethodVariableAccess.of(current).storeAt(freeOffset));
stackManipulations.add(dispatcher.initialize());
for (OffsetMapping.Resolved offsetMapping : offsetMappings) {
stackManipulations.add(offsetMapping.apply(receiver, original, parameters, result, current, methodHandle, offsets, freeOffset));
}
stackManipulations.add(dispatcher.apply(receiver, original, methodHandle));
return new Simple(new StackManipulation.Compound(stackManipulations), returned);
}
/**
* A factory for creating a delegating step during a member substitution.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Factory implements Step.Factory {
/**
* A description of the method or constructor to delegate to.
*/
private final MethodDescription.InDefinedShape delegate;
/**
* The dispatcher to use for invoking the delegate.
*/
private final Dispatcher dispatcher;
/**
* The offset mappings to use.
*/
private final List offsetMappings;
/**
* Creates a new factory for a delegating step.
*
* @param delegate A description of the method or constructor to delegate to.
* @param dispatcher The dispatcher to use for invoking the delegate.
* @param factories The dispatcher to use for invoking the delegate.
*/
protected Factory(MethodDescription.InDefinedShape delegate, Dispatcher dispatcher, List extends OffsetMapping.Factory>> factories) {
Map> offsetMappings = new HashMap>();
for (OffsetMapping.Factory> factory : factories) {
offsetMappings.put(net.bytebuddy.description.type.TypeDescription.ForLoadedType.of(factory.getAnnotationType()), factory);
}
this.offsetMappings = new ArrayList(factories.size());
if (delegate.isMethod() && !delegate.isStatic()) {
OffsetMapping offsetMapping = null;
for (AnnotationDescription annotationDescription : delegate.getDeclaredAnnotations()) {
OffsetMapping.Factory> factory = offsetMappings.get(annotationDescription.getAnnotationType());
if (factory != null) {
@SuppressWarnings("unchecked") OffsetMapping current = factory.make(delegate, (AnnotationDescription.Loadable) annotationDescription.prepare(factory.getAnnotationType()));
if (offsetMapping == null) {
offsetMapping = current;
} else {
throw new IllegalStateException(delegate + " is bound to both " + current + " and " + offsetMapping);
}
}
}
this.offsetMappings.add(offsetMapping == null
? new OffsetMapping.ForThisReference(delegate.getDeclaringType().asGenericType(), null, Source.SUBSTITUTED_ELEMENT, false)
: offsetMapping);
}
for (int index = 0; index < delegate.getParameters().size(); index++) {
ParameterDescription.InDefinedShape parameterDescription = delegate.getParameters().get(index);
OffsetMapping offsetMapping = null;
for (AnnotationDescription annotationDescription : parameterDescription.getDeclaredAnnotations()) {
OffsetMapping.Factory> factory = offsetMappings.get(annotationDescription.getAnnotationType());
if (factory != null) {
@SuppressWarnings("unchecked") OffsetMapping current = factory.make(parameterDescription, (AnnotationDescription.Loadable) annotationDescription.prepare(factory.getAnnotationType()));
if (offsetMapping == null) {
offsetMapping = current;
} else {
throw new IllegalStateException(parameterDescription + " is bound to both " + current + " and " + offsetMapping);
}
}
}
this.offsetMappings.add(offsetMapping == null
? new OffsetMapping.ForArgument(parameterDescription.getType(), index, null, Source.SUBSTITUTED_ELEMENT, false)
: offsetMapping);
}
this.delegate = delegate;
this.dispatcher = dispatcher;
}
/**
* {@inheritDoc}
*/
public Step make(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
List targets = new ArrayList(offsetMappings.size());
for (OffsetMapping offsetMapping : offsetMappings) {
targets.add(offsetMapping.resolve(assigner, typing, instrumentedType, instrumentedMethod));
}
return new ForDelegation(delegate.getReturnType(), dispatcher.resolve(instrumentedType, instrumentedMethod), targets);
}
}
/**
* An offset mapping for binding a parameter or dispatch target for the method or constructor that is delegated to.
*/
public interface OffsetMapping {
/**
* Resolves an offset mapping for a given instrumented method.
*
* @param assigner The assigner to use.
* @param typing The typing to use if no explicit typing is specified.
* @param instrumentedType The instrumented type.
* @param instrumentedMethod The instrumented method.
* @return A resolved version of this offset mapping.
*/
OffsetMapping.Resolved resolve(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod);
/**
* An offset mapping that was resolved for a given instrumented type and method.
*/
interface Resolved {
/**
* Applies this offset mapping.
*
* @param receiver The target type of the invoked delegate.
* @param original The substituted element.
* @param parameters The parameters that are supplied to the substituted expression.
* @param result The resulting type of the substituted expression.
* @param current The type of the value that was produced by the previous step in the substitution chain.
* @param methodHandle A method handle that represents the substituted element.
* @param offsets The offsets of the supplied parameters.
* @param offset The offset of the value that was produced by the previous step.
* @return An appropriate stack manipulation.
*/
StackManipulation apply(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
TypeDescription.Generic current,
JavaConstant.MethodHandle methodHandle,
Map offsets,
int offset);
/**
* An offset mapping that loads a stack manipulation.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForStackManipulation implements OffsetMapping.Resolved {
/**
* The stack manipulation to load.
*/
private final StackManipulation stackManipulation;
/**
* Creates a resolved offset mapping for a stack manipulation.
*
* @param stackManipulation The stack manipulation to load.
*/
public ForStackManipulation(StackManipulation stackManipulation) {
this.stackManipulation = stackManipulation;
}
/**
* {@inheritDoc}
*/
public StackManipulation apply(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
TypeDescription.Generic current,
JavaConstant.MethodHandle methodHandle,
Map offsets,
int offset) {
return stackManipulation;
}
}
}
/**
* A factory for creating an offset mapping based on an annotation on a parameter, method or constructor.
*
* @param The type of the annotation.
*/
interface Factory {
/**
* Returns the type of the annotation for this factory.
*
* @return The type of the annotation for this factory.
*/
Class getAnnotationType();
/**
* Creates an offset mapping for an annotation that was found on a non-static method.
*
* @param target The method that is the delegated to.
* @param annotation The annotation that was found on the method.
* @return An appropriate offset mapping.
*/
OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation);
/**
* Creates an offset mapping for a parameter of the method or constructor that is the delegation target.
*
* @param target The parameter that is bound to an expression.
* @param annotation The annotation that was found on the parameter.
* @return An appropriate offset mapping.
*/
OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation);
/**
* An abstract base implementation of a factory for an offset mapping.
*
* @param The type of the represented annotation.
*/
abstract class AbstractBase implements OffsetMapping.Factory {
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
return make(target.getDeclaringType().asGenericType(), annotation);
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
return make(target.getType(), annotation);
}
/**
* Returns an offset mapping for the bound method target or parameter.
*
* @param target The declaring type of a non-static method or a parameter type.
* @param annotation The annotation that was found on the method or parameter.
* @return An appropriate offset mapping.
*/
protected abstract OffsetMapping make(TypeDescription.Generic target, AnnotationDescription.Loadable annotation);
}
/**
* A factory for an offset mapping that does not support binding a method target.
*
* @param The type of the represented annotation.
*/
abstract class WithParameterSupportOnly implements OffsetMapping.Factory {
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
throw new UnsupportedOperationException("This factory does not support binding a method receiver");
}
}
/**
* A simple factory for an offset mapping.
*
* @param The type of the represented annotation.
*/
@HashCodeAndEqualsPlugin.Enhance
class Simple extends OffsetMapping.Factory.AbstractBase {
/**
* The type of the bound annotation.
*/
private final Class annotationType;
/**
* The offset mapping to return.
*/
private final OffsetMapping offsetMapping;
/**
* Creates a simple factory for an offset mapping.
*
* @param annotationType The type of the bound annotation.
* @param offsetMapping The offset mapping to return.
*/
public Simple(Class annotationType, OffsetMapping offsetMapping) {
this.annotationType = annotationType;
this.offsetMapping = offsetMapping;
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return annotationType;
}
@Override
protected OffsetMapping make(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) {
return offsetMapping;
}
}
}
/**
* An offset mapping that resolves a given stack manipulation.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForStackManipulation implements OffsetMapping {
/**
* The stack manipulation to apply.
*/
private final StackManipulation stackManipulation;
/**
* The type of the value that is produced by the stack manipulation.
*/
private final TypeDescription.Generic typeDescription;
/**
* The type of the parameter or method target that is bound by this mapping.
*/
private final TypeDescription.Generic targetType;
/**
* Creates a new offset mapping for a stack manipulation.
*
* @param stackManipulation The stack manipulation to apply.
* @param typeDescription The type of the value that is produced by the stack manipulation.
* @param targetType The type of the parameter or method target that is bound by this mapping.
*/
public ForStackManipulation(StackManipulation stackManipulation, TypeDescription.Generic typeDescription, TypeDescription.Generic targetType) {
this.targetType = targetType;
this.stackManipulation = stackManipulation;
this.typeDescription = typeDescription;
}
/**
* Resolves an offset mapping that binds the provided annotation type to a given constant value.
*
* @param annotationType The annotation type to bind.
* @param value The constant value being bound or {@code null}.
* @param The type of the annotation.
* @return An appropriate factory for an offset mapping.
*/
public static OffsetMapping.Factory of(Class annotationType, @MaybeNull Object value) {
return value == null
? new OffsetMapping.ForStackManipulation.OfDefaultValue(annotationType)
: new OffsetMapping.ForStackManipulation.Factory(annotationType, ConstantValue.Simple.wrap(value));
}
/**
* {@inheritDoc}
*/
public OffsetMapping.Resolved resolve(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new ForStackManipulation.Resolved(assigner, typing, stackManipulation, typeDescription, targetType);
}
/**
* A resolved offset mapping for a stack manipulation.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Resolved implements OffsetMapping.Resolved {
/**
* The assigner to use.
*/
private final Assigner assigner;
/**
* The typing to apply.
*/
private final Assigner.Typing typing;
/**
* The stack manipulation to apply.
*/
private final StackManipulation stackManipulation;
/**
* The type of the value that is produced by the stack manipulation.
*/
private final TypeDescription.Generic typeDescription;
/**
* The type of the parameter or method target that is bound by this mapping.
*/
private final TypeDescription.Generic targetType;
/**
* Creates a resolved offset mapping for a given stack manipulation.
*
* @param assigner The assigner to use.
* @param typing The typing to apply.
* @param stackManipulation The stack manipulation to apply.
* @param typeDescription The type of the value that is produced by the stack manipulation.
* @param targetType The type of the parameter or method target that is bound by this mapping.
*/
protected Resolved(Assigner assigner,
Assigner.Typing typing,
StackManipulation stackManipulation,
TypeDescription.Generic typeDescription,
TypeDescription.Generic targetType) {
this.assigner = assigner;
this.typing = typing;
this.stackManipulation = stackManipulation;
this.typeDescription = typeDescription;
this.targetType = targetType;
}
/**
* {@inheritDoc}
*/
public StackManipulation apply(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
TypeDescription.Generic current,
JavaConstant.MethodHandle methodHandle,
Map offsets,
int offset) {
StackManipulation assignment = assigner.assign(typeDescription, targetType, typing);
if (!assignment.isValid()) {
throw new IllegalStateException("Cannot assign " + typeDescription + " to " + targetType);
}
return new StackManipulation.Compound(stackManipulation, assignment);
}
}
/**
* A factory that binds the default value of the annotated parameter, i.e. {@code null} for reference types
* or the specific version of {@code 0} for primitive types.
*
* @param The type of the annotation.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class OfDefaultValue implements OffsetMapping.Factory {
/**
* The annotation type.
*/
private final Class annotationType;
/**
* Creates a new factory for binding a default value.
*
* @param annotationType The annotation type.
*/
public OfDefaultValue(Class annotationType) {
this.annotationType = annotationType;
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return annotationType;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
throw new UnsupportedOperationException("This factory does not support binding a method receiver");
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
return new ForStackManipulation(DefaultValue.of(target.getType()), target.getType(), target.getType());
}
}
/**
* A factory that binds a given annotation property to the parameter.
*
* @param The type of the annotation.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class OfAnnotationProperty extends OffsetMapping.Factory.WithParameterSupportOnly {
/**
* The annotation type.
*/
private final Class annotationType;
/**
* The annotation property to resolve.
*/
private final MethodDescription.InDefinedShape property;
/**
* Creates a factory for assigning an annotation property to the annotated parameter.
*
* @param annotationType The annotation type.
* @param property The annotation property to resolve.
*/
protected OfAnnotationProperty(Class annotationType, MethodDescription.InDefinedShape property) {
this.annotationType = annotationType;
this.property = property;
}
/**
* Resolves an offset mapping factory where the provided property is assigned to any parameter that
* is annotated with the given annotation.
*
* @param annotationType The annotation type.
* @param property The name of the property on the
* @param The type of the annotation from which the property is read.
* @return An appropriate factory for an offset mapping.
*/
public static OffsetMapping.Factory of(Class annotationType, String property) {
if (!annotationType.isAnnotation()) {
throw new IllegalArgumentException("Not an annotation type: " + annotationType);
}
try {
return new ForStackManipulation.OfAnnotationProperty(annotationType, new MethodDescription.ForLoadedMethod(annotationType.getMethod(property)));
} catch (NoSuchMethodException exception) {
throw new IllegalArgumentException("Cannot find a property " + property + " on " + annotationType, exception);
}
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return annotationType;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
ConstantValue value = ConstantValue.Simple.wrapOrNull(annotation.getValue(property).resolve());
if (value == null) {
throw new IllegalStateException("Not a constant value property: " + property);
}
return new ForStackManipulation(value.toStackManipulation(), value.getTypeDescription().asGenericType(), target.getType());
}
}
/**
* Assigns a value to the annotated parameter that is deserialized from a given input.
*
* @param The type of the annotation.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class OfSerializedConstant extends OffsetMapping.Factory.AbstractBase {
/**
* The annotation type.
*/
private final Class annotationType;
/**
* A stack manipulation that represents the deserialization.
*/
private final StackManipulation deserialization;
/**
* A description of the type that is returned as a result of the deserialization.
*/
private final TypeDescription.Generic typeDescription;
/**
* Creates a factory that creates an offset mapping for a value that is deserialized.
*
* @param annotationType The annotation type.
* @param deserialization A stack manipulation that represents the deserialization.
* @param typeDescription A description of the type that is returned as a result of the deserialization.
*/
protected OfSerializedConstant(Class annotationType, StackManipulation deserialization, TypeDescription.Generic typeDescription) {
this.annotationType = annotationType;
this.deserialization = deserialization;
this.typeDescription = typeDescription;
}
/**
* Creates a factory for an offset mapping that deserializes a given value that is then assigned to the annotated parameter or used as a method target.
*
* @param type The annotation type.
* @param value The serialized value.
* @param targetType The type of the value that is deserialized.
* @param The type of the annotation.
* @param The type of the serialized value.
* @return An appropriate factory for an offset mapping.
*/
public static OffsetMapping.Factory of(Class type, U value, Class super U> targetType) {
if (!targetType.isInstance(value)) {
throw new IllegalArgumentException(value + " is no instance of " + targetType);
}
return new ForStackManipulation.OfSerializedConstant(type, SerializedConstant.of(value), net.bytebuddy.description.type.TypeDescription.ForLoadedType.of(targetType).asGenericType());
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return annotationType;
}
@Override
protected OffsetMapping make(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) {
return new ForStackManipulation(deserialization, typeDescription, target);
}
}
/**
* A factory that invokes a method dynamically and assignes the result to the annotated parameter.
*
* @param The type of the annotation.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class OfDynamicInvocation extends OffsetMapping.Factory.AbstractBase {
/**
* The annotation type.
*/
private final Class annotationType;
/**
* The bootstrap method to use.
*/
private final MethodDescription.InDefinedShape bootstrapMethod;
/**
* The constants to provide to the bootstrap method.
*/
private final List extends JavaConstant> arguments;
/**
* Creates a factory for an offset mapping that assigns the result of a dynamic method invocation.
*
* @param annotationType The annotation type.
* @param bootstrapMethod The bootstrap method to use.
* @param arguments The constants to provide to the bootstrap method.
*/
public OfDynamicInvocation(Class annotationType, MethodDescription.InDefinedShape bootstrapMethod, List extends JavaConstant> arguments) {
this.annotationType = annotationType;
this.bootstrapMethod = bootstrapMethod;
this.arguments = arguments;
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return annotationType;
}
@Override
protected OffsetMapping make(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) {
if (!target.isInterface()) {
throw new IllegalArgumentException(target + " is not an interface");
} else if (!target.getInterfaces().isEmpty()) {
throw new IllegalArgumentException(target + " must not extend other interfaces");
} else if (!target.isPublic()) {
throw new IllegalArgumentException(target + " is mot public");
}
MethodList> methodCandidates = target.getDeclaredMethods().filter(isAbstract());
if (methodCandidates.size() != 1) {
throw new IllegalArgumentException(target + " must declare exactly one abstract method");
}
return new OffsetMapping.ForStackManipulation(MethodInvocation.invoke(bootstrapMethod).dynamic(methodCandidates.getOnly().getInternalName(),
target.asErasure(),
Collections.emptyList(),
arguments), target, target);
}
}
/**
* A factory to produce an offset mapping based upon a stack manipulation..
*
* @param The type of the annotation.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Factory extends OffsetMapping.Factory.AbstractBase {
/**
* The annotation type.
*/
private final Class annotationType;
/**
* The stack manipulation that produces the assigned value.
*/
private final StackManipulation stackManipulation;
/**
* The type of the value that is produced by the stack manipulation.
*/
private final TypeDescription.Generic typeDescription;
/**
* Creates a factory for a given constant value.
*
* @param annotationType The value to assign to the parameter.
* @param value The value that is bound.
*/
public Factory(Class annotationType, ConstantValue value) {
this(annotationType, value.toStackManipulation(), value.getTypeDescription().asGenericType());
}
/**
* Creates a factory for a given stack manipulation.
*
* @param annotationType The value to assign to the parameter.
* @param stackManipulation The stack manipulation that produces the assigned value.
* @param typeDescription The type of the value that is produced by the stack manipulation.
*/
public Factory(Class annotationType, StackManipulation stackManipulation, TypeDescription.Generic typeDescription) {
this.annotationType = annotationType;
this.stackManipulation = stackManipulation;
this.typeDescription = typeDescription;
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return annotationType;
}
@Override
protected OffsetMapping make(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) {
return new ForStackManipulation(stackManipulation, typeDescription, target);
}
}
}
/**
* An offset mapping that assigns an argument of either the instrumented
* method or the substituted expression.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForArgument implements OffsetMapping {
/**
* A description of the targeted type.
*/
private final TypeDescription.Generic targetType;
/**
* The index of the parameter.
*/
private final int index;
/**
* The typing to use or {@code null} if the global typing setting should be applied.
*/
@MaybeNull
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
private final Assigner.Typing typing;
/**
* The source providing the argument.
*/
private final Source source;
/**
* {@code true} if {@code null} or a primitive {@code 0} should be assigned to the parameter
* if the provided index is not available.
*/
private final boolean optional;
/**
* Creates a new offset mapping for an argument to either the substituted expression or the instrumented method.
*
* @param targetType A description of the targeted type.
* @param index The index of the parameter.
* @param typing The typing to use or {@code null} if the global typing setting should be applied.
* @param source The source providing the argument.
* @param optional {@code true} if {@code null} or a primitive {@code 0} should be assigned to the parameter
* if the provided index is not available.
*/
public ForArgument(TypeDescription.Generic targetType, int index, @MaybeNull Assigner.Typing typing, Source source, boolean optional) {
this.targetType = targetType;
this.index = index;
this.typing = typing;
this.source = source;
this.optional = optional;
}
/**
* {@inheritDoc}
*/
public OffsetMapping.Resolved resolve(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new ForArgument.Resolved(targetType, index, this.typing == null ? typing : this.typing, source, optional, assigner, instrumentedMethod);
}
/**
* A factory for creating an offset mapping for a parameter value of either the instrumented
* method or the substituted element.
*/
protected enum Factory implements OffsetMapping.Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The {@link Argument#value()} property.
*/
private static final MethodDescription.InDefinedShape ARGUMENT_VALUE;
/**
* The {@link Argument#typing()} property.
*/
private static final MethodDescription.InDefinedShape ARGUMENT_TYPING;
/**
* The {@link Argument#source()} property.
*/
private static final MethodDescription.InDefinedShape ARGUMENT_SOURCE;
/**
* The {@link Argument#optional()} property.
*/
private static final MethodDescription.InDefinedShape ARGUMENT_OPTIONAL;
/*
* Resolves all annotation properties.
*/
static {
MethodList methods = net.bytebuddy.description.type.TypeDescription.ForLoadedType.of(Argument.class).getDeclaredMethods();
ARGUMENT_VALUE = methods.filter(named("value")).getOnly();
ARGUMENT_TYPING = methods.filter(named("typing")).getOnly();
ARGUMENT_SOURCE = methods.filter(named("source")).getOnly();
ARGUMENT_OPTIONAL = methods.filter(named("optional")).getOnly();
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return Argument.class;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
return new ForArgument(target.getDeclaringType().asGenericType(),
annotation.getValue(ARGUMENT_VALUE).resolve(Integer.class),
annotation.getValue(ARGUMENT_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class),
annotation.getValue(ARGUMENT_SOURCE).resolve(EnumerationDescription.class).load(Source.class),
annotation.getValue(ARGUMENT_OPTIONAL).resolve(Boolean.class));
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
int index = annotation.getValue(ARGUMENT_VALUE).resolve(Integer.class);
if (index < 0) {
throw new IllegalStateException("Cannot assign negative parameter index " + index + " for " + target);
}
return new ForArgument(target.getType(),
index,
annotation.getValue(ARGUMENT_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class),
annotation.getValue(ARGUMENT_SOURCE).resolve(EnumerationDescription.class).load(Source.class),
annotation.getValue(ARGUMENT_OPTIONAL).resolve(Boolean.class));
}
}
/**
* A resolved offset mapping to the parameter of either the instrumented method or
* the substituted element.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Resolved implements OffsetMapping.Resolved {
/**
* The targeted type.
*/
private final TypeDescription.Generic targetType;
/**
* The index of the parameter.
*/
private final int index;
/**
* The typing to use when assigning.
*/
private final Assigner.Typing typing;
/**
* The source providing the argument.
*/
private final Source source;
/**
* {@code true} if {@code null} or a primitive {@code 0} should be assigned to the parameter
* if the provided index is not available.
*/
private final boolean optional;
/**
* The assigner to use.
*/
private final Assigner assigner;
/**
* The instrumented method.
*/
private final MethodDescription instrumentedMethod;
/**
* Creates a resolved offset mapping for assigning a parameter.
*
* @param targetType The targeted type.
* @param index The index of the parameter.
* @param typing The typing to use when assigning.
* @param source The source providing the argument.
* @param optional {@code true} if {@code null} or a primitive {@code 0} should be assigned
* to the parameter if the provided index is not available.
* @param assigner The assigner to use.
* @param instrumentedMethod The instrumented method.
*/
protected Resolved(TypeDescription.Generic targetType,
int index,
Assigner.Typing typing,
Source source,
boolean optional,
Assigner assigner,
MethodDescription instrumentedMethod) {
this.targetType = targetType;
this.index = index;
this.typing = typing;
this.source = source;
this.optional = optional;
this.assigner = assigner;
this.instrumentedMethod = instrumentedMethod;
}
/**
* {@inheritDoc}
*/
public StackManipulation apply(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
TypeDescription.Generic current,
JavaConstant.MethodHandle methodHandle,
Map offsets,
int offset) {
Source.Value value = source.argument(index, parameters, offsets, original, instrumentedMethod);
if (value != null) {
StackManipulation assignment = assigner.assign(value.getTypeDescription(), targetType, typing);
if (!assignment.isValid()) {
throw new IllegalStateException("Cannot assign " + value.getTypeDescription() + " to " + targetType);
}
return new StackManipulation.Compound(MethodVariableAccess.of(value.getTypeDescription()).loadFrom(value.getOffset()), assignment);
} else if (optional) {
return DefaultValue.of(targetType);
} else {
throw new IllegalStateException("No argument with index " + index + " available for " + original);
}
}
}
}
/**
* An offset mapping that assigns the {@code this} reference.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForThisReference implements OffsetMapping {
/**
* The targeted type.
*/
private final TypeDescription.Generic targetType;
/**
* The typing to use or {@code null} if implicit typing.
*/
@MaybeNull
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
private final Assigner.Typing typing;
/**
* The source providing the reference.
*/
private final Source source;
/**
* {@code true} if {@code null} or a primitive {@code 0} should be assigned to the parameter
* if no {@code this} reference is available.
*/
private final boolean optional;
/**
* Creates an offset mapping that resolves the {@code this} reference.
*
* @param targetType The targeted type.
* @param typing The typing to use or {@code null} if implicit typing.
* @param source The source providing the reference.
* @param optional {@code true} if {@code null} or a primitive {@code 0} should be assigned
* to the parameter if no {@code this} reference is available.
*/
public ForThisReference(TypeDescription.Generic targetType, @MaybeNull Assigner.Typing typing, Source source, boolean optional) {
this.targetType = targetType;
this.typing = typing;
this.source = source;
this.optional = optional;
}
/**
* {@inheritDoc}
*/
public ForThisReference.Resolved resolve(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new ForThisReference.Resolved(targetType, this.typing == null ? typing : this.typing, source, optional, assigner, instrumentedMethod);
}
/**
* A resolved offset mapping for resolving the {@code this} reference.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Resolved implements OffsetMapping.Resolved {
/**
* The targeted type.
*/
private final TypeDescription.Generic targetType;
/**
* The typing to use..
*/
private final Assigner.Typing typing;
/**
* The source providing the reference.
*/
private final Source source;
/**
* {@code true} if {@code null} or a primitive {@code 0} should be assigned to the parameter
* if no {@code this} reference is available.
*/
private final boolean optional;
/**
* The assigner to use.
*/
private final Assigner assigner;
/**
* The instrumented method.
*/
private final MethodDescription instrumentedMethod;
/**
* Creates a resolved offset mapping for assigning the {@code this} reference.
*
* @param targetType The targeted type.
* @param typing The typing to use.
* @param source The source providing the reference.
* @param optional {@code true} if {@code null} or a primitive {@code 0} should be assigned
* to the parameter if no {@code this} reference is available.
* @param assigner The assigner to use.
* @param instrumentedMethod The instrumented method.
*/
protected Resolved(TypeDescription.Generic targetType,
Assigner.Typing typing,
Source source,
boolean optional,
Assigner assigner,
MethodDescription instrumentedMethod) {
this.targetType = targetType;
this.typing = typing;
this.source = source;
this.optional = optional;
this.assigner = assigner;
this.instrumentedMethod = instrumentedMethod;
}
/**
* {@inheritDoc}
*/
public StackManipulation apply(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
TypeDescription.Generic current,
JavaConstant.MethodHandle methodHandle,
Map offsets,
int offset) {
Source.Value value = source.self(parameters, offsets, original, instrumentedMethod);
if (value != null) {
StackManipulation assignment = assigner.assign(value.getTypeDescription(), targetType, typing);
if (!assignment.isValid()) {
throw new IllegalStateException("Cannot assign " + value.getTypeDescription() + " to " + targetType);
}
return new StackManipulation.Compound(MethodVariableAccess.of(value.getTypeDescription()).loadFrom(value.getOffset()), assignment);
} else if (optional) {
return DefaultValue.of(targetType);
} else {
throw new IllegalStateException("No this reference available for " + original);
}
}
}
/**
* A factory for creating an offset mapping for binding a {@link This} reference.
*/
protected enum Factory implements OffsetMapping.Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The {@link This#typing()} property.
*/
private static final MethodDescription.InDefinedShape THIS_TYPING;
/**
* The {@link This#source()} reference.
*/
private static final MethodDescription.InDefinedShape THIS_SOURCE;
/**
* The {@link This#optional()} property.
*/
private static final MethodDescription.InDefinedShape THIS_OPTIONAL;
/*
* Resolves the annotation properties.
*/
static {
MethodList methods = net.bytebuddy.description.type.TypeDescription.ForLoadedType.of(This.class).getDeclaredMethods();
THIS_TYPING = methods.filter(named("typing")).getOnly();
THIS_SOURCE = methods.filter(named("source")).getOnly();
THIS_OPTIONAL = methods.filter(named("optional")).getOnly();
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return This.class;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
return new ForThisReference(target.getDeclaringType().asGenericType(),
annotation.getValue(THIS_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class),
annotation.getValue(THIS_SOURCE).resolve(EnumerationDescription.class).load(Source.class),
annotation.getValue(THIS_OPTIONAL).resolve(Boolean.class));
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
return new ForThisReference(target.getType(),
annotation.getValue(THIS_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class),
annotation.getValue(THIS_SOURCE).resolve(EnumerationDescription.class).load(Source.class),
annotation.getValue(THIS_OPTIONAL).resolve(Boolean.class));
}
}
}
/**
* An offset mapping that assigns an array containing all arguments to the annotated parameter.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForAllArguments implements OffsetMapping {
/**
* The component type of the annotated parameter.
*/
private final TypeDescription.Generic targetComponentType;
/**
* The typing to use or {@code null} if implicit typing.
*/
@MaybeNull
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
private final Assigner.Typing typing;
/**
* The source providing the reference.
*/
private final Source source;
/**
* {@code true} if the {@code this} reference should be included in the created array, if available.
*/
private final boolean includeSelf;
/**
* {@code true} if {@code null} should be assigned to the parameter if no arguments are available.
*/
private final boolean nullIfEmpty;
/**
* Creates a new offset mapping for an array containing all supplied arguments.
*
* @param targetComponentType The component type of the annotated parameter.
* @param typing The typing to use or {@code null} if implicit typing.
* @param source The source providing the reference.
* @param includeSelf {@code true} if the {@code this} reference should be included in the created array, if available.
* @param nullIfEmpty {@code true} if {@code null} should be assigned to the parameter if no arguments are available.
*/
public ForAllArguments(TypeDescription.Generic targetComponentType, @MaybeNull Assigner.Typing typing, Source source, boolean includeSelf, boolean nullIfEmpty) {
this.targetComponentType = targetComponentType;
this.typing = typing;
this.source = source;
this.includeSelf = includeSelf;
this.nullIfEmpty = nullIfEmpty;
}
/**
* {@inheritDoc}
*/
public OffsetMapping.Resolved resolve(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new ForAllArguments.Resolved(targetComponentType, this.typing == null ? typing : this.typing, source, includeSelf, nullIfEmpty, assigner, instrumentedMethod);
}
/**
* A factory for creating an offset mapping containing all supplies arguments.
*/
protected enum Factory implements OffsetMapping.Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The {@link AllArguments#typing()} property.
*/
private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_TYPING;
/**
* The {@link AllArguments#source()} property.
*/
private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_SOURCE;
/**
* The {@link AllArguments#includeSelf()} property.
*/
private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_INCLUDE_SELF;
/**
* The {@link AllArguments#nullIfEmpty()} property.
*/
private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_NULL_IF_EMPTY;
/*
* Resolves all annotation properties.
*/
static {
MethodList methods = TypeDescription.ForLoadedType.of(AllArguments.class).getDeclaredMethods();
ALL_ARGUMENTS_TYPING = methods.filter(named("typing")).getOnly();
ALL_ARGUMENTS_SOURCE = methods.filter(named("source")).getOnly();
ALL_ARGUMENTS_INCLUDE_SELF = methods.filter(named("includeSelf")).getOnly();
ALL_ARGUMENTS_NULL_IF_EMPTY = methods.filter(named("nullIfEmpty")).getOnly();
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return AllArguments.class;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
throw new UnsupportedOperationException("This factory does not support binding a method receiver");
}
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming component type for array type.")
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
if (!target.getType().isArray()) {
throw new IllegalStateException("Expected array as parameter type for " + target);
}
return new ForAllArguments(target.getType().getComponentType(),
annotation.getValue(ALL_ARGUMENTS_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class),
annotation.getValue(ALL_ARGUMENTS_SOURCE).resolve(EnumerationDescription.class).load(Source.class),
annotation.getValue(ALL_ARGUMENTS_INCLUDE_SELF).resolve(Boolean.class),
annotation.getValue(ALL_ARGUMENTS_NULL_IF_EMPTY).resolve(Boolean.class));
}
}
/**
* A resolves offset mapping for an array containing all arguments.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Resolved implements OffsetMapping.Resolved {
/**
* The component type of the annotated parameter.
*/
private final TypeDescription.Generic targetComponentType;
/**
* The typing to use.
*/
private final Assigner.Typing typing;
/**
* The source providing the reference.
*/
private final Source source;
/**
* {@code true} if the {@code this} reference should be included in the created array, if available.
*/
private final boolean includeSelf;
/**
* {@code true} if {@code null} should be assigned to the parameter if no arguments are available.
*/
private final boolean nullIfEmpty;
/**
* The assigner to use.
*/
private final Assigner assigner;
/**
* The instrumented method.
*/
private final MethodDescription instrumentedMethod;
/**
* Creates a resolved version for an offset mapping of all arguments.
*
* @param targetComponentType The component type of the annotated parameter.
* @param typing The typing to use.
* @param source The source providing the reference.
* @param includeSelf {@code true} if the {@code this} reference should be included in the created array, if available.
* @param nullIfEmpty {@code true} if {@code null} should be assigned to the parameter if no arguments are available.
* @param assigner The assigner to use.
* @param instrumentedMethod The instrumented method.
*/
protected Resolved(TypeDescription.Generic targetComponentType,
Assigner.Typing typing,
Source source,
boolean includeSelf,
boolean nullIfEmpty,
Assigner assigner,
MethodDescription instrumentedMethod) {
this.targetComponentType = targetComponentType;
this.typing = typing;
this.source = source;
this.includeSelf = includeSelf;
this.nullIfEmpty = nullIfEmpty;
this.assigner = assigner;
this.instrumentedMethod = instrumentedMethod;
}
/**
* {@inheritDoc}
*/
public StackManipulation apply(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
TypeDescription.Generic current,
JavaConstant.MethodHandle methodHandle,
Map offsets,
int offset) {
List values = source.arguments(includeSelf, parameters, offsets, original, instrumentedMethod);
if (nullIfEmpty && values.isEmpty()) {
return NullConstant.INSTANCE;
} else {
List stackManipulations = new ArrayList();
for (Source.Value value : values) {
StackManipulation assignment = assigner.assign(value.getTypeDescription(), targetComponentType, typing);
if (!assignment.isValid()) {
throw new IllegalStateException("Cannot assign " + value.getTypeDescription() + " to " + targetComponentType);
}
stackManipulations.add(new StackManipulation.Compound(MethodVariableAccess.of(value.getTypeDescription()).loadFrom(value.getOffset()), assignment));
}
return ArrayFactory.forType(targetComponentType).withValues(stackManipulations);
}
}
}
}
/**
* An offset mapping resolving a method handle to invoke the original expression or the instrumented method.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForSelfCallHandle implements OffsetMapping {
/**
* The source providing the reference.
*/
private final Source source;
/**
* {@code true} if the handle should be bound to the original arguments.
*/
private final boolean bound;
/**
* Creates a new offset mapping for a self call handle.
*
* @param source The source providing the reference.
* @param bound {@code true} if the handle should be bound to the original arguments.
*/
public ForSelfCallHandle(Source source, boolean bound) {
this.source = source;
this.bound = bound;
}
/**
* {@inheritDoc}
*/
public OffsetMapping.Resolved resolve(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return bound ? new ForSelfCallHandle.Bound(source, instrumentedMethod) : new ForSelfCallHandle.Unbound(source, instrumentedMethod);
}
/**
* A factory for creating an offset mapping for binding a self call handle.
*/
protected enum Factory implements OffsetMapping.Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The {@link SelfCallHandle#source()} property.
*/
private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_SOURCE;
/**
* The {@link SelfCallHandle#bound()} property.
*/
private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_BOUND;
/*
* Resolves all annotation properties.
*/
static {
MethodList methods = TypeDescription.ForLoadedType.of(SelfCallHandle.class).getDeclaredMethods();
ALL_ARGUMENTS_SOURCE = methods.filter(named("source")).getOnly();
ALL_ARGUMENTS_BOUND = methods.filter(named("bound")).getOnly();
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return SelfCallHandle.class;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
throw new UnsupportedOperationException("This factory does not support binding a method receiver");
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
if (!target.getType().asErasure().isAssignableFrom(JavaType.METHOD_HANDLE.getTypeStub())) {
throw new IllegalStateException("Cannot assign method handle to " + target);
}
return new ForSelfCallHandle(
annotation.getValue(ALL_ARGUMENTS_SOURCE).resolve(EnumerationDescription.class).load(Source.class),
annotation.getValue(ALL_ARGUMENTS_BOUND).resolve(Boolean.class));
}
}
/**
* Resolves a bound self call handle for an offset mapping.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Bound implements OffsetMapping.Resolved {
/**
* The source providing the reference.
*/
private final Source source;
/**
* The instrumented method.
*/
private final MethodDescription instrumentedMethod;
/**
* Creates an offset mapping for a bound version of a self call handle.
*
* @param source The source providing the reference.
* @param instrumentedMethod The instrumented method.
*/
protected Bound(Source source, MethodDescription instrumentedMethod) {
this.source = source;
this.instrumentedMethod = instrumentedMethod;
}
/**
* {@inheritDoc}
*/
public StackManipulation apply(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
TypeDescription.Generic current,
JavaConstant.MethodHandle methodHandle,
Map offsets,
int offset) {
Source.Value dispatched = source.self(parameters, offsets, original, instrumentedMethod);
List values = source.arguments(false, parameters, offsets, original, instrumentedMethod);
List stackManipulations = new ArrayList(1 + (values.size()
+ (dispatched == null ? 0 : 2))
+ (values.isEmpty() ? 0 : 1));
stackManipulations.add(source.handle(methodHandle, instrumentedMethod).toStackManipulation());
if (dispatched != null) {
stackManipulations.add(MethodVariableAccess.of(dispatched.getTypeDescription()).loadFrom(dispatched.getOffset()));
stackManipulations.add(MethodInvocation.invoke(new MethodDescription.Latent(JavaType.METHOD_HANDLE.getTypeStub(), new MethodDescription.Token("bindTo",
Opcodes.ACC_PUBLIC,
JavaType.METHOD_HANDLE.getTypeStub().asGenericType(),
new TypeList.Generic.Explicit(TypeDefinition.Sort.describe(Object.class))))));
}
if (!values.isEmpty()) {
for (Source.Value value : values) {
stackManipulations.add(MethodVariableAccess.of(value.getTypeDescription()).loadFrom(value.getOffset()));
}
stackManipulations.add(MethodInvocation.invoke(new MethodDescription.Latent(JavaType.METHOD_HANDLES.getTypeStub(), new MethodDescription.Token("insertArguments",
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
JavaType.METHOD_HANDLE.getTypeStub().asGenericType(),
new TypeList.Generic.Explicit(JavaType.METHOD_HANDLE.getTypeStub(), TypeDefinition.Sort.describe(int.class), TypeDefinition.Sort.describe(Object[].class))))));
}
return new StackManipulation.Compound(stackManipulations);
}
}
/**
* Resolves an unbound self call handle for an offset mapping.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Unbound implements OffsetMapping.Resolved {
/**
* The source providing the reference.
*/
private final Source source;
/**
* The instrumented method.
*/
private final MethodDescription instrumentedMethod;
/**
* Creates an offset mapping for an unbound version of a self call handle.
*
* @param source The source providing the reference.
* @param instrumentedMethod The instrumented method.
*/
protected Unbound(Source source, MethodDescription instrumentedMethod) {
this.source = source;
this.instrumentedMethod = instrumentedMethod;
}
/**
* {@inheritDoc}
*/
public StackManipulation apply(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
TypeDescription.Generic current,
JavaConstant.MethodHandle methodHandle,
Map offsets,
int offset) {
return source.handle(methodHandle, instrumentedMethod).toStackManipulation();
}
}
}
/**
* An offset mapping for a field value.
*/
@HashCodeAndEqualsPlugin.Enhance
abstract class ForField implements OffsetMapping {
/**
* The {@link FieldValue#value()} property.
*/
private static final MethodDescription.InDefinedShape FIELD_VALUE_VALUE;
/**
* The {@link FieldValue#declaringType()} property.
*/
private static final MethodDescription.InDefinedShape FIELD_VALUE_DECLARING_TYPE;
/**
* The {@link FieldValue#typing()} property.
*/
private static final MethodDescription.InDefinedShape FIELD_VALUE_TYPING;
/*
* Resolves all annotation properties.
*/
static {
MethodList methods = TypeDescription.ForLoadedType.of(FieldValue.class).getDeclaredMethods();
FIELD_VALUE_VALUE = methods.filter(named("value")).getOnly();
FIELD_VALUE_DECLARING_TYPE = methods.filter(named("declaringType")).getOnly();
FIELD_VALUE_TYPING = methods.filter(named("typing")).getOnly();
}
/**
* A description of the targeted type.
*/
private final TypeDescription.Generic target;
/**
* The typing to use or {@code null} if implicit typing.
*/
@MaybeNull
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
private final Assigner.Typing typing;
/**
* Creates an offset mapping for a field value.
*
* @param target A description of the targeted type.
* @param typing The typing to use or {@code null} if implicit typing.
*/
protected ForField(TypeDescription.Generic target, @MaybeNull Assigner.Typing typing) {
this.target = target;
this.typing = typing;
}
/**
* {@inheritDoc}
*/
public OffsetMapping.Resolved resolve(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
FieldDescription fieldDescription = resolve(instrumentedType, instrumentedMethod);
if (!fieldDescription.isStatic() && instrumentedMethod.isStatic()) {
throw new IllegalStateException("Cannot access non-static field " + fieldDescription + " from static method " + instrumentedMethod);
}
StackManipulation assignment = assigner.assign(fieldDescription.getType(), target, this.typing == null ? typing : this.typing);
if (!assignment.isValid()) {
throw new IllegalStateException("Cannot assign " + fieldDescription + " to " + target);
}
return new OffsetMapping.Resolved.ForStackManipulation(new StackManipulation.Compound(fieldDescription.isStatic()
? StackManipulation.Trivial.INSTANCE
: MethodVariableAccess.loadThis(), FieldAccess.forField(fieldDescription).read(), assignment));
}
/**
* Resolves a description of the field being accessed.
*
* @param instrumentedType The instrumented type.
* @param instrumentedMethod The instrumented method.
* @return A description of the field being accessed.
*/
protected abstract FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod);
/**
* An offset mapping for an unresolved field value.
*/
@HashCodeAndEqualsPlugin.Enhance
public abstract static class Unresolved extends ForField {
/**
* Indicates that the name of the field should be inferred from the instrumented method's name as a bean property.
*/
protected static final String BEAN_PROPERTY = "";
/**
* The name of the field being accessed or an empty string if the name of the field should be inferred.
*/
private final String name;
/**
* Creates an offset mapping for the value of an unresolved field.
*
* @param target A description of the targeted type.
* @param typing The typing to use.
* @param name The name of the field being accessed or an empty string if the name of the field should be inferred.
*/
protected Unresolved(TypeDescription.Generic target, Assigner.Typing typing, String name) {
super(target, typing);
this.name = name;
}
@Override
protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
FieldLocator locator = fieldLocator(instrumentedType);
FieldLocator.Resolution resolution = name.equals(BEAN_PROPERTY)
? FieldLocator.Resolution.Simple.ofBeanAccessor(locator, instrumentedMethod)
: locator.locate(name);
if (!resolution.isResolved()) {
throw new IllegalStateException("Cannot locate field named " + name + " for " + instrumentedType);
} else {
return resolution.getField();
}
}
/**
* Creates a field locator for the instrumented type.
*
* @param instrumentedType The instrumented type.
* @return An appropriate field locator.
*/
protected abstract FieldLocator fieldLocator(TypeDescription instrumentedType);
/**
* An offset mapping for an unresolved field with an implicit declaring type.
*/
public static class WithImplicitType extends Unresolved {
/**
* Creates an offset mapping for an unresolved field value with an implicit declaring type.
*
* @param target A description of the targeted type.
* @param annotation The annotation describing the access.
*/
protected WithImplicitType(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) {
this(target,
annotation.getValue(FIELD_VALUE_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class),
annotation.getValue(FIELD_VALUE_VALUE).resolve(String.class));
}
/**
* Creates an offset mapping for the value of an unresolved field with an implicit declaring type.
*
* @param target A description of the targeted type.
* @param typing The typing to use.
* @param name The name of the field being accessed or an empty string if the name of the field should be inferred.
*/
public WithImplicitType(TypeDescription.Generic target, Assigner.Typing typing, String name) {
super(target, typing, name);
}
@Override
protected FieldLocator fieldLocator(TypeDescription instrumentedType) {
return new FieldLocator.ForClassHierarchy(instrumentedType);
}
}
/**
* An offset mapping for an unresolved field value with an explicit declaring type.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class WithExplicitType extends Unresolved {
/**
* The field's declaring type.
*/
private final TypeDescription declaringType;
/**
* Creates an offset mapping for the value of an unresolved field with an explicit declaring type.
*
* @param target A description of the targeted type.
* @param annotation The annotation describing the field access.
* @param declaringType The field's declaring type.
*/
protected WithExplicitType(TypeDescription.Generic target, AnnotationDescription.Loadable annotation, TypeDescription declaringType) {
this(target,
annotation.getValue(FIELD_VALUE_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class),
annotation.getValue(FIELD_VALUE_VALUE).resolve(String.class),
declaringType);
}
/**
* Creates an offset mapping for the value of an unresolved field with an explicit declaring type.
*
* @param target A description of the targeted type.
* @param typing The typing to use.
* @param name The name of the field being accessed or an empty string if the name of the field should be inferred.
* @param declaringType The field's declaring type.
*/
public WithExplicitType(TypeDescription.Generic target, Assigner.Typing typing, String name, TypeDescription declaringType) {
super(target, typing, name);
this.declaringType = declaringType;
}
@Override
protected FieldLocator fieldLocator(TypeDescription instrumentedType) {
if (!declaringType.represents(TargetType.class) && !instrumentedType.isAssignableTo(declaringType)) {
throw new IllegalStateException(declaringType + " is no super type of " + instrumentedType);
}
return new FieldLocator.ForExactType(TargetType.resolve(declaringType, instrumentedType));
}
}
/**
* A factory for creating an offset mapping for a field value.
*/
protected enum Factory implements OffsetMapping.Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return FieldValue.class;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
TypeDescription declaringType = annotation.getValue(FIELD_VALUE_DECLARING_TYPE).resolve(TypeDescription.class);
return declaringType.represents(void.class)
? new Unresolved.WithImplicitType(target.getDeclaringType().asGenericType(), annotation)
: new Unresolved.WithExplicitType(target.getDeclaringType().asGenericType(), annotation, declaringType);
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
TypeDescription declaringType = annotation.getValue(FIELD_VALUE_DECLARING_TYPE).resolve(TypeDescription.class);
return declaringType.represents(void.class)
? new Unresolved.WithImplicitType(target.getType(), annotation)
: new Unresolved.WithExplicitType(target.getType(), annotation, declaringType);
}
}
}
/**
* An offset mapping for a resolved field access.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Resolved extends ForField {
/**
* A description of the field being accessed.
*/
private final FieldDescription fieldDescription;
/**
* Creates a resolved offset mapping for a field access.
*
* @param target A description of the targeted type.
* @param typing The typing to use or {@code null} if implicit typing.
* @param fieldDescription A description of the field accessed.
*/
public Resolved(TypeDescription.Generic target, Assigner.Typing typing, FieldDescription fieldDescription) {
super(target, typing);
this.fieldDescription = fieldDescription;
}
@Override
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming declaring type for type member.")
protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
if (!fieldDescription.isStatic() && !fieldDescription.getDeclaringType().asErasure().isAssignableFrom(instrumentedType)) {
throw new IllegalStateException(fieldDescription + " is no member of " + instrumentedType);
} else if (!fieldDescription.isVisibleTo(instrumentedType)) {
throw new IllegalStateException("Cannot access " + fieldDescription + " from " + instrumentedType);
}
return fieldDescription;
}
/**
* A factory for creating a resolved offset mapping of a field value.
*
* @param The type of the annotation.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Factory extends OffsetMapping.Factory.AbstractBase {
/**
* The annotation type.
*/
private final Class annotationType;
/**
* The field being accessed.
*/
private final FieldDescription fieldDescription;
/**
* The typing to use.
*/
private final Assigner.Typing typing;
/**
* Creates a factory for reading a given field.
*
* @param annotationType The annotation type.
* @param fieldDescription The field being accessed.
*/
public Factory(Class annotationType, FieldDescription fieldDescription) {
this(annotationType, fieldDescription, Assigner.Typing.STATIC);
}
/**
* Creates a factory for reading a given field.
*
* @param annotationType The annotation type.
* @param fieldDescription The field being accessed.
* @param typing The typing to use.
*/
public Factory(Class annotationType, FieldDescription fieldDescription, Assigner.Typing typing) {
this.annotationType = annotationType;
this.fieldDescription = fieldDescription;
this.typing = typing;
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return annotationType;
}
@Override
protected OffsetMapping make(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) {
return new ForField.Resolved(target, typing, fieldDescription);
}
}
}
}
/**
* An offset mapping for a method handle representing a field getter or setter.
*/
@HashCodeAndEqualsPlugin.Enhance
abstract class ForFieldHandle implements OffsetMapping {
/**
* The type of access to the field.
*/
private final Access access;
/**
* Creates an offset mapping for a field getter or setter.
*
* @param access The type of access to the field.
*/
protected ForFieldHandle(Access access) {
this.access = access;
}
/**
* {@inheritDoc}
*/
public OffsetMapping.Resolved resolve(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
FieldDescription fieldDescription = resolve(instrumentedType, instrumentedMethod);
if (!fieldDescription.isStatic() && instrumentedMethod.isStatic()) {
throw new IllegalStateException("Cannot access non-static field " + fieldDescription + " from static method " + instrumentedMethod);
}
if (fieldDescription.isStatic()) {
return new OffsetMapping.Resolved.ForStackManipulation(access.resolve(fieldDescription.asDefined()).toStackManipulation());
} else {
return new OffsetMapping.Resolved.ForStackManipulation(new StackManipulation.Compound(
access.resolve(fieldDescription.asDefined()).toStackManipulation(), MethodVariableAccess.REFERENCE.loadFrom(THIS_REFERENCE),
MethodInvocation.invoke(new MethodDescription.Latent(JavaType.METHOD_HANDLE.getTypeStub(), new MethodDescription.Token("bindTo",
Opcodes.ACC_PUBLIC,
JavaType.METHOD_HANDLE.getTypeStub().asGenericType(),
new TypeList.Generic.Explicit(TypeDefinition.Sort.describe(Object.class)))))));
}
}
/**
* Resolves a description of the field being accessed.
*
* @param instrumentedType The instrumented type.
* @param instrumentedMethod The instrumented method.
* @return A description of the field being accessed.
*/
protected abstract FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod);
/**
* The type of access to the field.
*/
public enum Access {
/**
* Describes a field getter.
*/
GETTER {
@Override
protected JavaConstant.MethodHandle resolve(FieldDescription.InDefinedShape fieldDescription) {
return JavaConstant.MethodHandle.ofGetter(fieldDescription);
}
},
/**
* Describes a field setter.
*/
SETTER {
@Override
protected JavaConstant.MethodHandle resolve(FieldDescription.InDefinedShape fieldDescription) {
return JavaConstant.MethodHandle.ofSetter(fieldDescription);
}
};
/**
* Resolves a handle for the represented field access.
*
* @param fieldDescription The field that is being accessed.
* @return An appropriate method handle.
*/
protected abstract JavaConstant.MethodHandle resolve(FieldDescription.InDefinedShape fieldDescription);
}
/**
* An offset mapping for an unresolved field handle.
*/
@HashCodeAndEqualsPlugin.Enhance
public abstract static class Unresolved extends ForFieldHandle {
/**
* Indicates that the field's name should be resolved as a bean property.
*/
protected static final String BEAN_PROPERTY = "";
/**
* The name of the field or an empty string if the name should be resolved from the instrumented method.
*/
private final String name;
/**
* Creates an offset mapping for an unresolved field handle.
*
* @param access The type of access to the field.
* @param name The name of the field or an empty string if the name should be resolved from the instrumented method.
*/
public Unresolved(Access access, String name) {
super(access);
this.name = name;
}
@Override
protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
FieldLocator locator = fieldLocator(instrumentedType);
FieldLocator.Resolution resolution = name.equals(BEAN_PROPERTY)
? FieldLocator.Resolution.Simple.ofBeanAccessor(locator, instrumentedMethod)
: locator.locate(name);
if (!resolution.isResolved()) {
throw new IllegalStateException("Cannot locate field named " + name + " for " + instrumentedType);
} else {
return resolution.getField();
}
}
/**
* Resolves a field locator for the instrumented type.
*
* @param instrumentedType The instrumented type.
* @return Returns an appropriate field locator.
*/
protected abstract FieldLocator fieldLocator(TypeDescription instrumentedType);
/**
* An offset mapping for an unresolved field handle with an implicit declaring type.
*/
public static class WithImplicitType extends Unresolved {
/**
* Creates an offset mapping for an unresolved field handle with an implicit declaring type.
*
* @param access The type of access to the field.
* @param name The name of the field or an empty string if the name should be resolved from the instrumented method.
*/
public WithImplicitType(Access access, String name) {
super(access, name);
}
@Override
protected FieldLocator fieldLocator(TypeDescription instrumentedType) {
return new FieldLocator.ForClassHierarchy(instrumentedType);
}
}
/**
* An offset mapping for an unresolved field handle with an explicit declaring type.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class WithExplicitType extends Unresolved {
/**
* The field's declaring type.
*/
private final TypeDescription declaringType;
/**
* Creates an offset mapping for an unresolved field handle with an explicit declaring type.
*
* @param access The type of access to the field.
* @param name The name of the field or an empty string if the name should be resolved from the instrumented method.
* @param declaringType The field's declaring type.
*/
public WithExplicitType(Access access, String name, TypeDescription declaringType) {
super(access, name);
this.declaringType = declaringType;
}
@Override
protected FieldLocator fieldLocator(TypeDescription instrumentedType) {
if (!declaringType.represents(TargetType.class) && !instrumentedType.isAssignableTo(declaringType)) {
throw new IllegalStateException(declaringType + " is no super type of " + instrumentedType);
}
return new FieldLocator.ForExactType(TargetType.resolve(declaringType, instrumentedType));
}
}
/**
* A factory for creating a method handle representing a getter for the targeted field.
*/
protected enum GetterFactory implements OffsetMapping.Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The {@link FieldGetterHandle#value()} method.
*/
private static final MethodDescription.InDefinedShape FIELD_GETTER_HANDLE_VALUE;
/**
* The {@link FieldGetterHandle#declaringType()} method.
*/
private static final MethodDescription.InDefinedShape FIELD_GETTER_HANDLE_DECLARING_TYPE;
/*
* Resolves all annotation properties.
*/
static {
MethodList methods = TypeDescription.ForLoadedType.of(FieldGetterHandle.class).getDeclaredMethods();
FIELD_GETTER_HANDLE_VALUE = methods.filter(named("value")).getOnly();
FIELD_GETTER_HANDLE_DECLARING_TYPE = methods.filter(named("declaringType")).getOnly();
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return FieldGetterHandle.class;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
throw new UnsupportedOperationException("This factory does not support binding a method receiver");
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
if (!target.getType().asErasure().isAssignableFrom(JavaType.METHOD_HANDLE.getTypeStub())) {
throw new IllegalStateException("Cannot assign method handle to " + target);
}
TypeDescription declaringType = annotation.getValue(FIELD_GETTER_HANDLE_DECLARING_TYPE).resolve(TypeDescription.class);
return declaringType.represents(void.class)
? new ForFieldHandle.Unresolved.WithImplicitType(Access.GETTER, annotation.getValue(FIELD_GETTER_HANDLE_VALUE).resolve(String.class))
: new ForFieldHandle.Unresolved.WithExplicitType(Access.GETTER, annotation.getValue(FIELD_GETTER_HANDLE_VALUE).resolve(String.class), declaringType);
}
}
/**
* A factory for creating a method handle representing a setter for the targeted field.
*/
protected enum SetterFactory implements OffsetMapping.Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The {@link FieldGetterHandle#value()} method.
*/
private static final MethodDescription.InDefinedShape FIELD_SETTER_HANDLE_VALUE;
/**
* The {@link FieldGetterHandle#declaringType()} method.
*/
private static final MethodDescription.InDefinedShape FIELD_SETTER_HANDLE_DECLARING_TYPE;
/*
* Resolves the annotation properties.
*/
static {
MethodList methods = TypeDescription.ForLoadedType.of(FieldSetterHandle.class).getDeclaredMethods();
FIELD_SETTER_HANDLE_VALUE = methods.filter(named("value")).getOnly();
FIELD_SETTER_HANDLE_DECLARING_TYPE = methods.filter(named("declaringType")).getOnly();
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return FieldSetterHandle.class;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
throw new UnsupportedOperationException("This factory does not support binding a method receiver");
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
if (!target.getType().asErasure().isAssignableFrom(JavaType.METHOD_HANDLE.getTypeStub())) {
throw new IllegalStateException("Cannot assign method handle to " + target);
}
TypeDescription declaringType = annotation.getValue(FIELD_SETTER_HANDLE_DECLARING_TYPE).resolve(TypeDescription.class);
return declaringType.represents(void.class)
? new ForFieldHandle.Unresolved.WithImplicitType(Access.SETTER, annotation.getValue(FIELD_SETTER_HANDLE_VALUE).resolve(String.class))
: new ForFieldHandle.Unresolved.WithExplicitType(Access.SETTER, annotation.getValue(FIELD_SETTER_HANDLE_VALUE).resolve(String.class), declaringType);
}
}
}
/**
* An offset mapping for a resolved field handle.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Resolved extends OffsetMapping.ForFieldHandle {
/**
* The field that is being accessed.
*/
private final FieldDescription fieldDescription;
/**
* Creates a resolved mapping for a field access handle.
*
* @param access The type of access.
* @param fieldDescription The field that is being accessed.
*/
public Resolved(Access access, FieldDescription fieldDescription) {
super(access);
this.fieldDescription = fieldDescription;
}
@Override
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming declaring type for type member.")
protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
if (!fieldDescription.isStatic() && !fieldDescription.getDeclaringType().asErasure().isAssignableFrom(instrumentedType)) {
throw new IllegalStateException(fieldDescription + " is no member of " + instrumentedType);
} else if (!fieldDescription.isVisibleTo(instrumentedType)) {
throw new IllegalStateException("Cannot access " + fieldDescription + " from " + instrumentedType);
}
return fieldDescription;
}
/**
* A factory to create an offset mapping for a resolved field handle.
*
* @param The type of the annotation.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Factory implements OffsetMapping.Factory {
/**
* The annotation type.
*/
private final Class annotationType;
/**
* The field being accessed.
*/
private final FieldDescription fieldDescription;
/**
* The type of access.
*/
private final Access access;
/**
* Creates a new factory for a field access handle.
*
* @param annotationType The annotation type.
* @param fieldDescription The field being accessed.
* @param access The type of access.
*/
public Factory(Class annotationType, FieldDescription fieldDescription, Access access) {
this.annotationType = annotationType;
this.fieldDescription = fieldDescription;
this.access = access;
}
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return annotationType;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
throw new UnsupportedOperationException("This factory does not support binding a method receiver");
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
if (!target.getType().asErasure().isAssignableFrom(JavaType.METHOD_HANDLE.getTypeStub())) {
throw new IllegalStateException("Cannot assign method handle to " + target);
}
return new ForFieldHandle.Resolved(access, fieldDescription);
}
}
}
}
/**
* An offset mapping for describing a representation of the substituted element or the instrumented method.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForOrigin implements OffsetMapping {
/**
* The sort of the origin representation.
*/
private final Sort sort;
/**
* The source providing the reference.
*/
private final Source source;
/**
* Creates an offset mapping a representation of the substituted element or instrumented method.
*
* @param sort The sort of the origin representation.
* @param source The source providing the reference.
*/
protected ForOrigin(Sort sort, Source source) {
this.sort = sort;
this.source = source;
}
/**
* {@inheritDoc}
*/
public OffsetMapping.Resolved resolve(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new ForOrigin.Resolved(sort, source, instrumentedMethod);
}
/**
* The sort of the origin expression.
*/
protected enum Sort {
/**
* Represents the supplied value as a {@link Method}.
*/
METHOD {
@Override
protected boolean isRepresentable(ByteCodeElement.Member original) {
return original instanceof MethodDescription && ((MethodDescription) original).isMethod();
}
@Override
protected StackManipulation resolve(ByteCodeElement.Member original, List parameterTypes, TypeDescription returnType) {
return MethodConstant.of(((MethodDescription) original).asDefined());
}
},
/**
* Represents the supplied value as a {@link Constructor}.
*/
CONSTRUCTOR {
@Override
protected boolean isRepresentable(ByteCodeElement.Member original) {
return original instanceof MethodDescription && ((MethodDescription) original).isConstructor();
}
@Override
protected StackManipulation resolve(ByteCodeElement.Member original, List parameterTypes, TypeDescription returnType) {
return MethodConstant.of(((MethodDescription) original).asDefined());
}
},
/**
* Represents the supplied value as a {@link Field}.
*/
FIELD {
@Override
protected boolean isRepresentable(ByteCodeElement.Member original) {
return original instanceof FieldDescription;
}
@Override
protected StackManipulation resolve(ByteCodeElement.Member original, List parameterTypes, TypeDescription returnType) {
return new FieldConstant(((FieldDescription) original).asDefined());
}
},
/**
* Represents the supplied value as a {@code java.lang.reflect.Executable}.
*/
EXECUTABLE {
@Override
protected boolean isRepresentable(ByteCodeElement.Member original) {
return original instanceof MethodDescription;
}
@Override
protected StackManipulation resolve(ByteCodeElement.Member original, List parameterTypes, TypeDescription returnType) {
return MethodConstant.of(((MethodDescription) original).asDefined());
}
},
/**
* Represents the supplied value as a {@link Class}.
*/
TYPE {
@Override
protected boolean isRepresentable(ByteCodeElement.Member original) {
return true;
}
@Override
protected StackManipulation resolve(ByteCodeElement.Member original, List parameterTypes, TypeDescription returnType) {
return ClassConstant.of(original.getDeclaringType().asErasure());
}
},
/**
* Represents the supplied value as a {@code java.lang.invoke.MethodHandles.Lookup}.
*/
LOOKUP {
@Override
protected boolean isRepresentable(ByteCodeElement.Member original) {
return true;
}
@Override
protected StackManipulation resolve(ByteCodeElement.Member original, List parameterTypes, TypeDescription returnType) {
return MethodInvocation.lookup();
}
},
/**
* Represents the supplied value as a {@code java.lang.invoke.MethodHandle}.
*/
METHOD_HANDLE {
@Override
protected boolean isRepresentable(ByteCodeElement.Member original) {
return true;
}
@Override
protected StackManipulation resolve(ByteCodeElement.Member original, List parameterTypes, TypeDescription returnType) {
JavaConstant.MethodHandle handle;
if (original instanceof MethodDescription) {
handle = JavaConstant.MethodHandle.of(((MethodDescription) original).asDefined());
} else if (original instanceof FieldDescription) {
handle = returnType.represents(void.class)
? JavaConstant.MethodHandle.ofSetter(((FieldDescription) original).asDefined())
: JavaConstant.MethodHandle.ofGetter(((FieldDescription) original).asDefined());
} else {
throw new IllegalStateException("Unexpected byte code element: " + original);
}
return handle.toStackManipulation();
}
},
/**
* Represents the supplied value as a {@code java.lang.invoke.MethodType}.
*/
METHOD_TYPE {
@Override
protected boolean isRepresentable(ByteCodeElement.Member original) {
return true;
}
@Override
protected StackManipulation resolve(ByteCodeElement.Member original, List parameterTypes, TypeDescription returnType) {
return JavaConstant.MethodType.of(returnType, parameterTypes).toStackManipulation();
}
},
/**
* Represents the supplied value as its {@link Object#toString()} representation.
*/
STRING {
@Override
protected boolean isRepresentable(ByteCodeElement.Member original) {
return true;
}
@Override
protected StackManipulation resolve(ByteCodeElement.Member original, List parameterTypes, TypeDescription returnType) {
return new TextConstant(original.toString());
}
};
/**
* Checks if the supplied member can be represented by this sort.
*
* @param original The byte code element to check.
* @return {@code true} if the supplied element can be represented.
*/
protected abstract boolean isRepresentable(ByteCodeElement.Member original);
/**
* Creates a stack manipulation for the supplied byte code element.
*
* @param original The substituted element.
* @param parameterTypes The parameter types.
* @param returnType The return type.
* @return A stack manipulation loading the supplied byte code element's representation onto the stack.
*/
protected abstract StackManipulation resolve(ByteCodeElement.Member original, List parameterTypes, TypeDescription returnType);
}
/**
* A factory for an offset mapping that describes a representation of the substituted element or instrumented method.
*/
protected enum Factory implements OffsetMapping.Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The {@link Origin#source()} property.
*/
private static final MethodDescription.InDefinedShape ORIGIN_TYPE = TypeDescription.ForLoadedType.of(Origin.class)
.getDeclaredMethods()
.filter(named("source"))
.getOnly();
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return Origin.class;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
throw new UnsupportedOperationException("This factory does not support binding a method receiver");
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
Sort sort;
if (target.getType().asErasure().represents(Class.class)) {
sort = Sort.TYPE;
} else if (target.getType().asErasure().represents(Method.class)) {
sort = Sort.METHOD;
} else if (target.getType().asErasure().represents(Constructor.class)) {
sort = Sort.CONSTRUCTOR;
} else if (target.getType().asErasure().represents(Field.class)) {
sort = Sort.FIELD;
} else if (JavaType.EXECUTABLE.getTypeStub().equals(target.getType().asErasure())) {
sort = Sort.EXECUTABLE;
} else if (JavaType.METHOD_HANDLE.getTypeStub().equals(target.getType().asErasure())) {
sort = Sort.METHOD_HANDLE;
} else if (JavaType.METHOD_TYPE.getTypeStub().equals(target.getType().asErasure())) {
sort = Sort.METHOD_TYPE;
} else if (JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().equals(target.getType().asErasure())) {
sort = Sort.LOOKUP;
} else if (target.getType().asErasure().isAssignableFrom(String.class)) {
sort = Sort.STRING;
} else {
throw new IllegalStateException("Non-supported type " + target.getType() + " for @Origin annotation");
}
return new ForOrigin(sort, annotation.getValue(ORIGIN_TYPE).resolve(EnumerationDescription.class).load(Source.class));
}
}
/**
* A resolved offset mapping for a representation of the substituted expression or instrumented method.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Resolved implements OffsetMapping.Resolved {
/**
* The sort of the origin representation.
*/
private final Sort sort;
/**
* The source providing the reference.
*/
private final Source source;
/**
* The instrumented method.
*/
private final MethodDescription instrumentedMethod;
/**
* Creates a resolved version of an offset mapping for describing the substituted expression or instrumented method.
*
* @param sort The sort of the origin representation.
* @param source The source providing the reference.
* @param instrumentedMethod The instrumented method.
*/
protected Resolved(Sort sort, Source source, MethodDescription instrumentedMethod) {
this.sort = sort;
this.source = source;
this.instrumentedMethod = instrumentedMethod;
}
/**
* {@inheritDoc}
*/
public StackManipulation apply(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
TypeDescription.Generic current,
JavaConstant.MethodHandle methodHandle,
Map offsets,
int offset) {
if (!source.isRepresentable(sort, original, instrumentedMethod)) {
throw new IllegalStateException("Cannot represent " + sort + " for " + source + " in " + instrumentedMethod);
}
return source.resolve(sort, original, parameters, result, instrumentedMethod);
}
}
}
/**
* An offset mapping that assigns a stub value.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForStubValue implements OffsetMapping {
/**
* The source providing the reference.
*/
private final Source source;
/**
* Creates an offset mapping for a stub value.
*
* @param source The source providing the reference.
*/
protected ForStubValue(Source source) {
this.source = source;
}
/**
* {@inheritDoc}
*/
public OffsetMapping.Resolved resolve(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new Resolved(source, instrumentedMethod);
}
/**
* A resolved offset mapping for an offset mapping of a stub value.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Resolved implements OffsetMapping.Resolved {
/**
* The source providing the reference.
*/
private final Source source;
/**
* The instrumented method.
*/
private final MethodDescription instrumentedMethod;
/**
* Creates a resolved version of an offset mapping for a stub value.
*
* @param source The source providing the reference.
* @param instrumentedMethod The instrumented method.
*/
protected Resolved(Source source, MethodDescription instrumentedMethod) {
this.source = source;
this.instrumentedMethod = instrumentedMethod;
}
/**
* {@inheritDoc}
*/
public StackManipulation apply(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
TypeDescription.Generic current,
JavaConstant.MethodHandle methodHandle,
Map offsets,
int offset) {
return DefaultValue.of(source.handle(methodHandle, instrumentedMethod).getReturnType());
}
}
/**
* A factory for creating an offset mapping for a stub value.
*/
@HashCodeAndEqualsPlugin.Enhance
protected enum Factory implements OffsetMapping.Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The {@link StubValue#source()} property.
*/
private static final MethodDescription.InDefinedShape STUB_VALUE_SOURCE = TypeDescription.ForLoadedType.of(StubValue.class)
.getDeclaredMethods()
.filter(named("source"))
.getOnly();
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return StubValue.class;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
throw new UnsupportedOperationException("This factory does not support binding a method receiver");
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
if (!target.getType().represents(Object.class)) {
throw new IllegalStateException("Expected " + target + " to declare an Object type");
}
return new ForStubValue(annotation.getValue(STUB_VALUE_SOURCE).resolve(EnumerationDescription.class).load(Source.class));
}
}
}
/**
* An offset mapping that assigns the value of the previous chain instruction.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForCurrent implements OffsetMapping {
/**
* The type of the targeted expression.
*/
private final TypeDescription.Generic targetType;
/**
* The typing to use or {@code null} if implicit typing.
*/
@MaybeNull
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
private final Assigner.Typing typing;
/**
* Creates an offset mapping for the previous chain instruction.
*
* @param targetType The type of the targeted expression.
* @param typing The typing to use or {@code null} if implicit typing.
*/
public ForCurrent(TypeDescription.Generic targetType, @MaybeNull Assigner.Typing typing) {
this.targetType = targetType;
this.typing = typing;
}
/**
* {@inheritDoc}
*/
public OffsetMapping.Resolved resolve(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new ForCurrent.Resolved(targetType, assigner, this.typing == null ? typing : this.typing);
}
/**
* A factory for creating an offset mapping for assigning the result of the previous chain instruction.
*/
protected enum Factory implements OffsetMapping.Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The {@link Current#typing()} property.
*/
private static final MethodDescription.InDefinedShape CURRENT_TYPING = TypeDescription.ForLoadedType.of(Current.class)
.getDeclaredMethods()
.filter(named("typing"))
.getOnly();
/**
* {@inheritDoc}
*/
public Class getAnnotationType() {
return Current.class;
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(MethodDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
return new ForCurrent(target.getDeclaringType().asGenericType(),
annotation.getValue(CURRENT_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class));
}
/**
* {@inheritDoc}
*/
public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation) {
return new ForCurrent(target.getType(), annotation.getValue(CURRENT_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class));
}
}
/**
* A resolved offset mapping for assigning the previous chain instruction.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Resolved implements OffsetMapping.Resolved {
/**
* The type of the targeted expression.
*/
private final TypeDescription.Generic targetType;
/**
* The assigner to use.
*/
private final Assigner assigner;
/**
* The typing to use.
*/
private final Assigner.Typing typing;
/**
* Creates a resolved offset mapping for assigning the previous chain instruction.
*
* @param targetType The type of the targeted expression.
* @param assigner The assigner to use.
* @param typing The typing to use.
*/
public Resolved(TypeDescription.Generic targetType, Assigner assigner, Assigner.Typing typing) {
this.targetType = targetType;
this.assigner = assigner;
this.typing = typing;
}
/**
* {@inheritDoc}
*/
public StackManipulation apply(TypeDescription receiver,
ByteCodeElement.Member original,
TypeList.Generic parameters,
TypeDescription.Generic result,
TypeDescription.Generic current,
JavaConstant.MethodHandle methodHandle,
Map offsets,
int offset) {
StackManipulation assignment = assigner.assign(current, targetType, typing);
if (!assignment.isValid()) {
throw new IllegalStateException("Cannot assign " + current + " to " + targetType);
}
return new StackManipulation.Compound(MethodVariableAccess.of(current).loadFrom(offset), assignment);
}
}
}
}
/**
* A dispatcher for invoking a delegation method.
*/
protected interface Dispatcher {
/**
* Resolves a dispatcher for a given instrumented type and method.
*
* @param instrumentedType The instrumented type.
* @param instrumentedMethod The instrumented method.
* @return A resolved version of this dispatcher.
*/
Dispatcher.Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod);
/**
* A dispatcher that has been resolved for a given instrumented type and method.
*/
interface Resolved {
StackManipulation initialize();
/**
* Creates a stack manipulation for a given substitution target.
*
* @param receiver The type upon which the substituted element is invoked upon.
* @param original The substituted element.
* @param methodHandle A method handle that describes the invocation.
* @return A stack manipulation that executes the represented delegation.
*/
StackManipulation apply(TypeDescription receiver, ByteCodeElement.Member original, JavaConstant.MethodHandle methodHandle);
}
/**
* A factory for creating a dispatcher.
*/
interface Factory {
/**
* Creates a dispatcher for a given delegation method.
*
* @param delegate The method or constructor to delegate to.
* @return An appropriate dispatcher.
*/
Dispatcher make(MethodDescription.InDefinedShape delegate);
}
/**
* A dispatcher that invokes a delegate method directly.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForRegularInvocation implements Dispatcher, Dispatcher.Resolved {
/**
* The delegation method.
*/
private final MethodDescription delegate;
/**
* Creates a dispatcher for a regular method invocation.
*
* @param delegate The delegation method.
*/
protected ForRegularInvocation(MethodDescription delegate) {
this.delegate = delegate;
}
/**
* {@inheritDoc}
*/
public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return this;
}
/**
* {@inheritDoc}
*/
public StackManipulation initialize() {
return delegate.isConstructor()
? new StackManipulation.Compound(TypeCreation.of(delegate.getDeclaringType().asErasure()), Duplication.SINGLE)
: StackManipulation.Trivial.INSTANCE;
}
/**
* {@inheritDoc}
*/
public StackManipulation apply(TypeDescription receiver, ByteCodeElement.Member original, JavaConstant.MethodHandle methodHandle) {
return MethodInvocation.invoke(delegate);
}
/**
* A factory for creating a dispatcher for a regular method invocation.
*/
protected enum Factory implements Dispatcher.Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Dispatcher make(MethodDescription.InDefinedShape delegate) {
return new ForRegularInvocation(delegate);
}
}
}
/**
* A method dispatcher that is using a dynamic method invocation.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForDynamicInvocation implements Dispatcher {
/**
* The bootstrap method.
*/
private final MethodDescription.InDefinedShape bootstrapMethod;
/**
* The delegation method.
*/
private final MethodDescription.InDefinedShape delegate;
/**
* A resolver for supplying arguments to the bootstrap method.
*/
private final BootstrapArgumentResolver resolver;
/**
* Creates a dispatcher for a dynamic method invocation.
*
* @param bootstrapMethod The bootstrap method.
* @param delegate The delegation method.
* @param resolver A resolver for supplying arguments to the bootstrap method.
*/
protected ForDynamicInvocation(MethodDescription.InDefinedShape bootstrapMethod, MethodDescription.InDefinedShape delegate, BootstrapArgumentResolver resolver) {
this.bootstrapMethod = bootstrapMethod;
this.delegate = delegate;
this.resolver = resolver;
}
/**
* Creates a dispatcher factory for a dynamic method invocation.
*
* @param bootstrapMethod The bootstrap method.
* @param resolverFactory A resolver for supplying arguments to the bootstrap method.
* @return An appropriate dispatcher factory.
*/
protected static Dispatcher.Factory of(MethodDescription.InDefinedShape bootstrapMethod, BootstrapArgumentResolver.Factory resolverFactory) {
if (!bootstrapMethod.isInvokeBootstrap()) {
throw new IllegalStateException("Not a bootstrap method: " + bootstrapMethod);
}
return new ForDynamicInvocation.Factory(bootstrapMethod, resolverFactory);
}
/**
* {@inheritDoc}
*/
public Dispatcher.Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new ForDynamicInvocation.Resolved(bootstrapMethod, delegate, resolver.resolve(instrumentedType, instrumentedMethod));
}
/**
* A resolved dispatcher for a dynamically bound method invocation.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Resolved implements Dispatcher.Resolved {
/**
* The bootstrap method.
*/
private final MethodDescription.InDefinedShape bootstrapMethod;
/**
* The delegation target.
*/
private final MethodDescription.InDefinedShape delegate;
/**
* The bootstrap argument resolver to use.
*/
private final BootstrapArgumentResolver.Resolved resolver;
/**
* Creates a resolved dispatcher of a dynamic method dispatcher.
*
* @param bootstrapMethod The bootstrap method.
* @param delegate The delegation target.
* @param resolver The bootstrap argument resolver to use.
*/
protected Resolved(MethodDescription.InDefinedShape bootstrapMethod, MethodDescription.InDefinedShape delegate, BootstrapArgumentResolver.Resolved resolver) {
this.bootstrapMethod = bootstrapMethod;
this.delegate = delegate;
this.resolver = resolver;
}
/**
* {@inheritDoc}
*/
public StackManipulation initialize() {
return StackManipulation.Trivial.INSTANCE;
}
/**
* {@inheritDoc}
*/
public StackManipulation apply(TypeDescription receiver, ByteCodeElement.Member original, JavaConstant.MethodHandle methodHandle) {
List constants = resolver.make(receiver, original, methodHandle);
if (!bootstrapMethod.isInvokeBootstrap(TypeList.Explicit.of(constants))) {
throw new IllegalArgumentException(bootstrapMethod + " is not accepting advice bootstrap arguments: " + constants);
}
return MethodInvocation.invoke(bootstrapMethod).dynamic(delegate.getInternalName(),
delegate.getReturnType().asErasure(),
delegate.getParameters().asTypeList().asErasures(),
constants);
}
}
/**
* A factory for a dynamic method invocation of the dispatcher method or constructor.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Factory implements Dispatcher.Factory {
/**
* The bootstrap method.
*/
private final MethodDescription.InDefinedShape bootstrapMethod;
/**
* A factory for a bootstrap argument resolver.
*/
private final BootstrapArgumentResolver.Factory resolverFactory;
/**
* Creates a new factory for a dispatcher using a dynamic method invocation.
*
* @param bootstrapMethod The bootstrap method.
* @param resolverFactory A factory for a bootstrap argument resolver.
*/
protected Factory(MethodDescription.InDefinedShape bootstrapMethod, BootstrapArgumentResolver.Factory resolverFactory) {
this.bootstrapMethod = bootstrapMethod;
this.resolverFactory = resolverFactory;
}
/**
* {@inheritDoc}
*/
public Dispatcher make(MethodDescription.InDefinedShape delegate) {
return new ForDynamicInvocation(bootstrapMethod, delegate, resolverFactory.make(delegate));
}
}
}
}
/**
* A resolver for supplying arguments to a bootstrap method which is binding the delegation method's invocation.
*/
public interface BootstrapArgumentResolver {
/**
* Resolves this resolver for a given instrumented type and method.
*
* @param instrumentedType The instrumented type.
* @param instrumentedMethod The instrumented method.
* @return A resolved version of this argument resolver.
*/
BootstrapArgumentResolver.Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod);
/**
* A resolved version of a bootstrap argument handler.
*/
interface Resolved {
/**
* Returns the constant values to supply to the bootstrap method.
*
* @param receiver The type upon which the substituted element is applied.
* @param original The substituted element.
* @param methodHandle A method handle that represents the substituted element.
* @return A list of constant values to supply to the bootstrap method.
*/
List make(TypeDescription receiver, ByteCodeElement.Member original, JavaConstant.MethodHandle methodHandle);
}
/**
* A factory for a bootstrap argument resolver.
*/
interface Factory {
/**
* Creates a bootstrap argument resolver for a given delegation method.
*
* @param delegate The method or constructor to which to delegate.
* @return An appropriate bootstrap argument resolver.
*/
BootstrapArgumentResolver make(MethodDescription.InDefinedShape delegate);
}
/**
* An implementation that supplies a default set of arguments to a bootstrap method. The arguments are:
*
* - A {@code java.lang.invoke.MethodHandles.Lookup} representing the source method.
* - A {@link String} representing the target's internal name.
* - A {@code java.lang.invoke.MethodType} representing the type that is requested for binding.
* - A {@link String} representation of the delegate's binary class name.
* - A {@link Class} representing the receiver type of the substituted element.
* - A {@link String} representing the internal name of the substituted element.
* - A {@code java.lang.invoke.MethodHandle} to the substituted element.
* - A {@link Class} describing the instrumented type.
* - A {@link String} representing the instrumented method or constructor.
*
*/
@HashCodeAndEqualsPlugin.Enhance
class ForDefaultValues implements BootstrapArgumentResolver {
/**
* The delegation target.
*/
private final MethodDescription.InDefinedShape delegate;
/**
* Creates a default bootstrap argument resolver.
*
* @param delegate The delegation target.
*/
protected ForDefaultValues(MethodDescription.InDefinedShape delegate) {
this.delegate = delegate;
}
/**
* {@inheritDoc}
*/
public BootstrapArgumentResolver.Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return new Resolved(delegate, instrumentedType, instrumentedMethod);
}
/**
* A resolved default bootstrap argument resolver.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Resolved implements BootstrapArgumentResolver.Resolved {
/**
* The delegation target.
*/
private final MethodDescription.InDefinedShape delegate;
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The instrumented method.
*/
private final MethodDescription instrumentedMethod;
/**
* Creates a resolved version of a bootstrap argument resolver.
*
* @param delegate The delegation target.
* @param instrumentedType The instrumented type.
* @param instrumentedMethod The instrumented method.
*/
protected Resolved(MethodDescription.InDefinedShape delegate, TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
this.delegate = delegate;
this.instrumentedType = instrumentedType;
this.instrumentedMethod = instrumentedMethod;
}
/**
* {@inheritDoc}
*/
public List make(TypeDescription receiver, ByteCodeElement.Member original, JavaConstant.MethodHandle methodHandle) {
if (instrumentedMethod.isTypeInitializer()) {
return Arrays.asList(JavaConstant.Simple.ofLoaded(delegate.getDeclaringType().getName()),
JavaConstant.Simple.of(receiver),
JavaConstant.Simple.ofLoaded(original.getInternalName()),
methodHandle,
JavaConstant.Simple.of(instrumentedType),
JavaConstant.Simple.ofLoaded(instrumentedMethod.getInternalName()));
} else {
return Arrays.asList(JavaConstant.Simple.ofLoaded(delegate.getDeclaringType().getName()),
JavaConstant.Simple.of(receiver),
JavaConstant.Simple.ofLoaded(original.getInternalName()),
methodHandle,
JavaConstant.Simple.of(instrumentedType),
JavaConstant.Simple.ofLoaded(instrumentedMethod.getInternalName()),
JavaConstant.MethodHandle.of(instrumentedMethod.asDefined()));
}
}
}
/**
* A factory for creating a default bootstrap argument resolver.
*/
public enum Factory implements BootstrapArgumentResolver.Factory {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public BootstrapArgumentResolver make(MethodDescription.InDefinedShape delegate) {
return new ForDefaultValues(delegate);
}
}
}
}
/**
* A factory for a {@link ForDelegation} which allows for a custom configuration.
*/
public static class WithCustomMapping {
/**
* The dispatcher factory to use.
*/
private final Dispatcher.Factory dispatcherFactory;
/**
* A mapping of offset mapping factories by their respective annotation type.
*/
private final Map, OffsetMapping.Factory>> offsetMappings;
/**
* Creates a factory for a {@link ForDelegation} with a custom value.
*
* @param dispatcherFactory The dispatcher factory to use.
* @param offsetMappings A mapping of offset mapping factories by their respective annotation type.
*/
protected WithCustomMapping(Dispatcher.Factory dispatcherFactory, Map, OffsetMapping.Factory>> offsetMappings) {
this.dispatcherFactory = dispatcherFactory;
this.offsetMappings = offsetMappings;
}
/**
* Binds the supplied annotation to a type constant of the supplied value. Constants can be strings, method handles, method types
* and any primitive or the value {@code null}.
*
* @param type The type of the annotation being bound.
* @param value The value to bind to the annotation or {@code null} to bind the parameter type's default value.
* @param The annotation type.
* @return A new builder for a delegate that considers the supplied annotation type during binding.
*/
public WithCustomMapping bind(Class type, @MaybeNull Object value) {
return bind(OffsetMapping.ForStackManipulation.of(type, value));
}
/**
* Binds the supplied annotation to the value of the supplied field. The field must be visible by the
* instrumented type and must be declared by a super type of the instrumented field.
*
* @param type The type of the annotation being bound.
* @param field The field to bind to this annotation.
* @param The annotation type.
* @return A new builder for a delegate that considers the supplied annotation type during binding.
*/
public WithCustomMapping bind(Class type, Field field) {
return bind(type, new FieldDescription.ForLoadedField(field));
}
/**
* Binds the supplied annotation to the value of the supplied field. The field must be visible by the
* instrumented type and must be declared by a super type of the instrumented field. The binding is defined
* as read-only and applied static typing.
*
* @param type The type of the annotation being bound.
* @param fieldDescription The field to bind to this annotation.
* @param The annotation type.
* @return A new builder for a delegate that considers the supplied annotation type during binding.
*/
public WithCustomMapping bind(Class type, FieldDescription fieldDescription) {
return bind(new OffsetMapping.ForField.Resolved.Factory(type, fieldDescription));
}
/**
* Binds the supplied annotation to the supplied type constant.
*
* @param type The type of the annotation being bound.
* @param value The type constant to bind.
* @param The annotation type.
* @return A new builder for a delegate that considers the supplied annotation type during binding.
*/
public WithCustomMapping bind(Class type, Class> value) {
return bind(type, TypeDescription.ForLoadedType.of(value));
}
/**
* Binds the supplied annotation to the supplied type constant.
*
* @param type The type of the annotation being bound.
* @param value The type constant to bind.
* @param The annotation type.
* @return A new builder for a delegate that considers the supplied annotation type during binding.
*/
public WithCustomMapping bind(Class type, TypeDescription value) {
return bind(new OffsetMapping.ForStackManipulation.Factory(type, ConstantValue.Simple.wrap(value)));
}
/**
* Binds the supplied annotation to the supplied enumeration constant.
*
* @param type The type of the annotation being bound.
* @param value The enumeration constant to bind.
* @param The annotation type.
* @return A new builder for a delegate that considers the supplied annotation type during binding.
*/
public WithCustomMapping bind(Class type, Enum> value) {
return bind(type, new EnumerationDescription.ForLoadedEnumeration(value));
}
/**
* Binds the supplied annotation to the supplied enumeration constant.
*
* @param type The type of the annotation being bound.
* @param value The enumeration constant to bind.
* @param The annotation type.
* @return A new builder for a delegate that considers the supplied annotation type during binding.
*/
public WithCustomMapping bind(Class type, EnumerationDescription value) {
return bind(new OffsetMapping.ForStackManipulation.Factory(type, ConstantValue.Simple.wrap(value)));
}
/**
* Binds the supplied annotation to the supplied fixed value.
*
* @param type The type of the annotation being bound.
* @param value The value to bind to this annotation.
* @param The annotation type.
* @return A new builder for a delegate that considers the supplied annotation type during binding.
*/
@SuppressWarnings("unchecked")
public