com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder Maven / Gradle / Ivy
package com.fitbur.mockito.bytebuddy.agent.builder;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import com.fitbur.mockito.bytebuddy.ByteBuddy;
import com.fitbur.mockito.bytebuddy.ClassFileVersion;
import com.fitbur.mockito.bytebuddy.asm.AsmVisitorWrapper;
import com.fitbur.mockito.bytebuddy.description.field.FieldDescription;
import com.fitbur.mockito.bytebuddy.description.method.MethodDescription;
import com.fitbur.mockito.bytebuddy.description.method.ParameterDescription;
import com.fitbur.mockito.bytebuddy.description.modifier.*;
import com.fitbur.mockito.bytebuddy.description.type.TypeDescription;
import com.fitbur.mockito.bytebuddy.dynamic.ClassFileLocator;
import com.fitbur.mockito.bytebuddy.dynamic.DynamicType;
import com.fitbur.mockito.bytebuddy.dynamic.loading.ClassInjector;
import com.fitbur.mockito.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import com.fitbur.mockito.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import com.fitbur.mockito.bytebuddy.dynamic.scaffold.InstrumentedType;
import com.fitbur.mockito.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
import com.fitbur.mockito.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import com.fitbur.mockito.bytebuddy.implementation.ExceptionMethod;
import com.fitbur.mockito.bytebuddy.implementation.Implementation;
import com.fitbur.mockito.bytebuddy.implementation.LoadedTypeInitializer;
import com.fitbur.mockito.bytebuddy.implementation.MethodCall;
import com.fitbur.mockito.bytebuddy.implementation.auxiliary.AuxiliaryType;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.*;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.assign.Assigner;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.assign.TypeCasting;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.collection.ArrayFactory;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.constant.ClassConstant;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.constant.IntegerConstant;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.constant.NullConstant;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.constant.TextConstant;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.member.FieldAccess;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.member.MethodInvocation;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.member.MethodReturn;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import com.fitbur.mockito.bytebuddy.matcher.ElementMatcher;
import com.fitbur.mockito.bytebuddy.pool.TypePool;
import com.fitbur.mockito.bytebuddy.utility.JavaConstant;
import com.fitbur.mockito.bytebuddy.utility.JavaModule;
import com.fitbur.mockito.bytebuddy.utility.JavaType;
import com.fitbur.mockito.bytebuddy.jar.asm.Label;
import com.fitbur.mockito.bytebuddy.jar.asm.MethodVisitor;
import com.fitbur.mockito.bytebuddy.jar.asm.Opcodes;
import com.fitbur.mockito.bytebuddy.jar.asm.Type;
import java.io.*;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import static com.fitbur.mockito.bytebuddy.matcher.ElementMatchers.*;
/**
*
* An agent builder provides a convenience API for defining a
* Java agent. By default,
* this transformation is applied by rebasing the type if not specified otherwise by setting a
* {@link TypeStrategy}.
*
*
* When defining several {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s, the agent builder always
* applies the transformers that were supplied with the last applicable matcher. Therefore, more general transformers
* should be defined first.
*
*/
public interface AgentBuilder {
/**
* Defines the given {@link com.fitbur.mockito.bytebuddy.ByteBuddy} instance to be used by the created agent.
*
* @param byteBuddy The Byte Buddy instance to be used.
* @return A new instance of this agent builder which makes use of the given {@code byteBuddy} instance.
*/
AgentBuilder with(ByteBuddy byteBuddy);
/**
* Defines the given {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Listener} to be notified by the created agent.
* The given listener is notified after any other listener that is already registered. If a listener is registered
* twice, it is also notified twice.
*
* @param listener The listener to be notified.
* @return A new instance of this agent builder which creates an agent that informs the given listener about
* events.
*/
AgentBuilder with(Listener listener);
/**
* Defines the use of the given type locator for locating binary data to given class names.
*
* @param typeLocator The type locator to use.
* @return A new instance of this agent builder which uses the given type locator for looking up class files.
*/
AgentBuilder with(TypeLocator typeLocator);
/**
* Defines how types should be transformed, e.g. if they should be rebased or redefined by the created agent.
*
* @param typeStrategy The type strategy to use.
* @return A new instance of this agent builder which uses the given type strategy.
*/
AgentBuilder with(TypeStrategy typeStrategy);
/**
* Assures that critical actions are performed using the supplied access control context.
*
* @param accessControlContext The access control context to be used for performing security critical action.
* @return A new instance of this agent builder which uses the given access control context for performing critical actions.
*/
AgentBuilder with(AccessControlContext accessControlContext);
/**
* Defines a given initialization strategy to be applied to generated types. An initialization strategy is responsible
* for setting up a type after it was loaded. This initialization must be performed after the transformation because
* a Java agent is only invoked before loading a type. By default, the initialization logic is added to a class's type
* initializer which queries a global object for any objects that are to be injected into the generated type.
*
* @param initializationStrategy The initialization strategy to use.
* @return A new instance of this agent builder that applies the given initialization strategy.
*/
AgentBuilder with(InitializationStrategy initializationStrategy);
/**
* Specifies a strategy for modifying types that were already loaded prior to the installation of this transformer.
*
* @param redefinitionStrategy The redefinition strategy to apply.
* @return A new instance of this agent builder that applies the given redefinition strategy.
*/
AgentBuilder with(RedefinitionStrategy redefinitionStrategy);
/**
*
* Enables or disables management of the JVM's {@code LambdaMetafactory} which is responsible for creating classes that
* implement lambda expressions. Without this feature enabled, classes that are represented by lambda expressions are
* not instrumented by the JVM such that Java agents have no effect on them when a lambda expression's class is loaded
* for the first time.
*
*
* When activating this feature, Byte Buddy instruments the {@code LambdaMetafactory} and takes over the responsibility
* of creating classes that represent lambda expressions. In doing so, Byte Buddy has the opportunity to apply the built
* class file transformer. If the current VM does not support lambda expressions, activating this feature has no effect.
*
*
* Important: If this feature is active, it is important to release the built class file transformer when
* deactivating it. Normally, it is sufficient to call {@link Instrumentation#removeTransformer(ClassFileTransformer)}.
* When this feature is enabled, it is however also required to invoke
* {@link LambdaInstrumentationStrategy#release(ClassFileTransformer, Instrumentation)}. Otherwise, the executing VMs class
* loader retains a reference to the class file transformer what can cause a memory leak.
*
*
* @param lambdaInstrumentationStrategy {@code true} if this feature should be enabled.
* @return A new instance of this agent builder where this feature is explicitly enabled or disabled.
*/
AgentBuilder with(LambdaInstrumentationStrategy lambdaInstrumentationStrategy);
/**
* Specifies a strategy to be used for resolving {@link TypeDescription} for any type handled by the created transformer.
*
* @param descriptionStrategy The description strategy to use.
* @return A new instance of this agent builder that applies the given description strategy.
*/
AgentBuilder with(DescriptionStrategy descriptionStrategy);
/**
* Enables class injection of auxiliary classes into the bootstrap class loader.
*
* @param instrumentation The instrumentation instance that is used for appending jar files to the
* bootstrap class path.
* @param folder The folder in which jar files of the injected classes are to be stored.
* @return An agent builder with bootstrap class loader class injection enabled.
*/
AgentBuilder enableBootstrapInjection(Instrumentation instrumentation, File folder);
/**
* Enables the use of the given native method prefix for instrumented methods. Note that this prefix is also
* applied when preserving non-native methods. The use of this prefix is also registered when installing the
* final agent with an {@link java.lang.instrument.Instrumentation}.
*
* @param prefix The prefix to be used.
* @return A new instance of this agent builder which uses the given native method prefix.
*/
AgentBuilder enableNativeMethodPrefix(String prefix);
/**
* Disables the use of a native method prefix for instrumented methods.
*
* @return A new instance of this agent builder which does not use a native method prefix.
*/
AgentBuilder disableNativeMethodPrefix();
/**
* Disables injection of auxiliary classes into the bootstrap class path.
*
* @return A new instance of this agent builder which does not apply bootstrap class loader injection.
*/
AgentBuilder disableBootstrapInjection();
/**
*
* Disables all implicit changes on a class file that Byte Buddy would apply for certain instrumentations. When
* using this option, it is no longer possible to rebase a method, i.e. intercepted methods are fully replaced. Furthermore,
* it is no longer possible to implicitly apply loaded type initializers for explicitly initializing the generated type.
*
*
* This is equivalent to setting {@link InitializationStrategy.NoOp} and {@link TypeStrategy.Default#REDEFINE_DECLARED_ONLY}
* as well as configuring the underlying {@link ByteBuddy} instance to use a {@link com.fitbur.mockito.bytebuddy.implementation.Implementation.Context.Disabled}.
*
*
* @return A new instance of this agent builder that does not apply any implicit changes to the received class file.
*/
AgentBuilder disableClassFormatChanges();
/**
* Assures that all modules of the supplied types are read by the module of any instrumented type. If the current VM does not support
* the Java module system, calling this method has no effect and this instance is returned.
*
* @param instrumentation The instrumentation instance that is used for adding a module read-dependency.
* @param type The types for which to assure their module-visibility from any instrumented class.
* @return A new instance of this agent builder that assures the supplied types module visibility.
* @see Listener.ModuleReadEdgeCompleting
*/
AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Class>... type);
/**
* Assures that all supplied modules are read by the module of any instrumented type.
*
* @param instrumentation The instrumentation instance that is used for adding a module read-dependency.
* @param module The modules for which to assure their module-visibility from any instrumented class.
* @return A new instance of this agent builder that assures the supplied types module visibility.
* @see Listener.ModuleReadEdgeCompleting
*/
AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, JavaModule... module);
/**
* Assures that all supplied modules are read by the module of any instrumented type.
*
* @param instrumentation The instrumentation instance that is used for adding a module read-dependency.
* @param modules The modules for which to assure their module-visibility from any instrumented class.
* @return A new instance of this agent builder that assures the supplied types module visibility.
* @see Listener.ModuleReadEdgeCompleting
*/
AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Collection extends JavaModule> modules);
/**
* Assures that all modules of the supplied types are read by the module of any instrumented type and vice versa.
* If the current VM does not support the Java module system, calling this method has no effect and this instance is returned.
*
* @param instrumentation The instrumentation instance that is used for adding a module read-dependency.
* @param type The types for which to assure their module-visibility from and to any instrumented class.
* @return A new instance of this agent builder that assures the supplied types module visibility.
* @see Listener.ModuleReadEdgeCompleting
*/
AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Class>... type);
/**
* Assures that all supplied modules are read by the module of any instrumented type and vice versa.
*
* @param instrumentation The instrumentation instance that is used for adding a module read-dependency.
* @param module The modules for which to assure their module-visibility from and to any instrumented class.
* @return A new instance of this agent builder that assures the supplied types module visibility.
* @see Listener.ModuleReadEdgeCompleting
*/
AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, JavaModule... module);
/**
* Assures that all supplied modules are read by the module of any instrumented type and vice versa.
*
* @param instrumentation The instrumentation instance that is used for adding a module read-dependency.
* @param modules The modules for which to assure their module-visibility from and to any instrumented class.
* @return A new instance of this agent builder that assures the supplied types module visibility.
* @see Listener.ModuleReadEdgeCompleting
*/
AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Collection extends JavaModule> modules);
/**
*
* Matches a type being loaded in order to apply the supplied {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s before loading this type.
* If several matchers positively match a type only the latest registered matcher is considered for transformation.
*
*
* If this matcher is chained with additional subsequent matchers, this matcher is always executed first whereas the following matchers are
* executed in the order of their execution. If any matcher indicates that a type is to be matched, none of the following matchers is still queried.
* This behavior can be changed by {@link Identified.Extendable#asDecorator()} where subsequent type matchers are also applied.
*
*
* Note: When applying a matcher, regard the performance implications by {@link AgentBuilder#ignore(ElementMatcher)}. The former
* matcher is applied first such that it makes sense to ignore name spaces that are irrelevant to instrumentation. If possible, it is
* also recommended, to exclude class loaders such as for example the bootstrap class loader by using
* {@link AgentBuilder#type(ElementMatcher, ElementMatcher)} instead.
*
*
* @param typeMatcher An {@link com.fitbur.mockito.bytebuddy.matcher.ElementMatcher} that is applied on the type being loaded that
* decides if the entailed {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s should
* be applied for that type.
* @return A definable that represents this agent builder which allows for the definition of one or several
* {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s to be applied when the given {@code typeMatcher}
* indicates a match.
*/
Identified.Narrowable type(ElementMatcher super TypeDescription> typeMatcher);
/**
*
* Matches a type being loaded in order to apply the supplied {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s before loading this type.
* If several matchers positively match a type only the latest registered matcher is considered for transformation.
*
*
* If this matcher is chained with additional subsequent matchers, this matcher is always executed first whereas the following matchers are
* executed in the order of their execution. If any matcher indicates that a type is to be matched, none of the following matchers is still queried.
* This behavior can be changed by {@link Identified.Extendable#asDecorator()} where subsequent type matchers are also applied.
*
*
* Note: When applying a matcher, regard the performance implications by {@link AgentBuilder#ignore(ElementMatcher)}. The former
* matcher is applied first such that it makes sense to ignore name spaces that are irrelevant to instrumentation. If possible, it
* is also recommended, to exclude class loaders such as for example the bootstrap class loader.
*
*
* @param typeMatcher An {@link com.fitbur.mockito.bytebuddy.matcher.ElementMatcher} that is applied on the type being
* loaded that decides if the entailed
* {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s should be applied for
* that type.
* @param classLoaderMatcher An {@link com.fitbur.mockito.bytebuddy.matcher.ElementMatcher} that is applied to the
* {@link java.lang.ClassLoader} that is loading the type being loaded. This matcher
* is always applied first where the type matcher is not applied in case that this
* matcher does not indicate a match.
* @return A definable that represents this agent builder which allows for the definition of one or several
* {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s to be applied when both the given
* {@code typeMatcher} and {@code classLoaderMatcher} indicate a match.
*/
Identified.Narrowable type(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher);
/**
*
* Matches a type being loaded in order to apply the supplied {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s before loading this type.
* If several matchers positively match a type only the latest registered matcher is considered for transformation.
*
*
* If this matcher is chained with additional subsequent matchers, this matcher is always executed first whereas the following matchers are
* executed in the order of their execution. If any matcher indicates that a type is to be matched, none of the following matchers is still queried.
* This behavior can be changed by {@link Identified.Extendable#asDecorator()} where subsequent type matchers are also applied.
*
*
* Note: When applying a matcher, regard the performance implications by {@link AgentBuilder#ignore(ElementMatcher)}. The former
* matcher is applied first such that it makes sense to ignore name spaces that are irrelevant to instrumentation. If possible, it
* is also recommended, to exclude class loaders such as for example the bootstrap class loader.
*
*
* @param typeMatcher An {@link com.fitbur.mockito.bytebuddy.matcher.ElementMatcher} that is applied on the type being
* loaded that decides if the entailed
* {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s should be applied for
* that type.
* @param classLoaderMatcher An {@link com.fitbur.mockito.bytebuddy.matcher.ElementMatcher} that is applied to the
* {@link java.lang.ClassLoader} that is loading the type being loaded. This matcher
* is always applied second where the type matcher is not applied in case that this
* matcher does not indicate a match.
* @param moduleMatcher An {@link com.fitbur.mockito.bytebuddy.matcher.ElementMatcher} that is applied to the {@link JavaModule}
* of the type being loaded. This matcher is always applied first where the class loader and
* type matchers are not applied in case that this matcher does not indicate a match. On a JVM
* that does not support the Java modules system, this matcher is not applied.
* @return A definable that represents this agent builder which allows for the definition of one or several
* {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s to be applied when both the given
* {@code typeMatcher} and {@code classLoaderMatcher} indicate a match.
*/
Identified.Narrowable type(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher);
/**
*
* Matches a type being loaded in order to apply the supplied {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s before loading this type.
* If several matchers positively match a type only the latest registered matcher is considered for transformation.
*
*
* If this matcher is chained with additional subsequent matchers, this matcher is always executed first whereas the following matchers are
* executed in the order of their execution. If any matcher indicates that a type is to be matched, none of the following matchers is still queried.
*
*
* Note: When applying a matcher, regard the performance implications by {@link AgentBuilder#ignore(ElementMatcher)}. The former
* matcher is applied first such that it makes sense to ignore name spaces that are irrelevant to instrumentation. If possible, it
* is also recommended, to exclude class loaders such as for example the bootstrap class loader.
*
*
* @param matcher A matcher that decides if the entailed {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s should be
* applied for a type that is being loaded.
* @return A definable that represents this agent builder which allows for the definition of one or several
* {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s to be applied when the given {@code matcher}
* indicates a match.
*/
Identified.Narrowable type(RawMatcher matcher);
/**
*
* Excludes any type that is matched by the provided matcher from instrumentation and considers types by all {@link ClassLoader}s.
* By default, Byte Buddy does not instrument synthetic types or types that are loaded by the bootstrap class loader.
*
*
* When ignoring a type, any subsequently chained matcher is applied after this matcher in the order of their registration. Also, if
* any matcher indicates that a type is to be ignored, none of the following chained matchers is executed.
*
*
* Note: For performance reasons, it is recommended to always include a matcher that excludes as many namespaces
* as possible. Byte Buddy can determine a type's name without parsing its class file and can therefore discard such
* types with minimal overhead. When a different property of a type - such as for example its modifiers or its annotations
* is accessed - Byte Buddy parses the class file lazily in order to allow for such a matching. Therefore, any exclusion
* of a name should always be done as a first step and even if it does not influence the selection of what types are
* matched. Without changing this property, the class file of every type is being parsed!
*
*
* Warning: If a type is loaded during the instrumentation of the same type, this causes the original call site that loads the type
* to remain unbound, causing a {@link LinkageError}. It is therefore important to not instrument types that may be loaded during the application
* of a {@link Transformer}. For this reason, it is not recommended to instrument classes of the bootstrap class loader that Byte Buddy might
* require for instrumenting a class or to instrument any of Byte Buddy's classes. If such instrumentation is desired, it is important to
* assert for each class that they are not loaded during instrumentation.
*
*
* @param typeMatcher A matcher that identifies types that should not be instrumented.
* @return A new instance of this agent builder that ignores all types that are matched by the provided matcher.
* All previous matchers for ignored types are discarded.
*/
Ignored ignore(ElementMatcher super TypeDescription> typeMatcher);
/**
*
* Excludes any type that is matched by the provided matcher and is loaded by a class loader matching the second matcher.
* By default, Byte Buddy does not instrument synthetic types, types within a {@code com.fitbur.mockito.bytebuddy.*} package or types that
* are loaded by the bootstrap class loader.
*
*
* When ignoring a type, any subsequently chained matcher is applied after this matcher in the order of their registration. Also, if
* any matcher indicates that a type is to be ignored, none of the following chained matchers is executed.
*
*
* Note: For performance reasons, it is recommended to always include a matcher that excludes as many namespaces
* as possible. Byte Buddy can determine a type's name without parsing its class file and can therefore discard such
* types with minimal overhead. When a different property of a type - such as for example its modifiers or its annotations
* is accessed - Byte Buddy parses the class file lazily in order to allow for such a matching. Therefore, any exclusion
* of a name should always be done as a first step and even if it does not influence the selection of what types are
* matched. Without changing this property, the class file of every type is being parsed!
*
*
* Warning: If a type is loaded during the instrumentation of the same type, this causes the original call site that loads the type
* to remain unbound, causing a {@link LinkageError}. It is therefore important to not instrument types that may be loaded during the application
* of a {@link Transformer}. For this reason, it is not recommended to instrument classes of the bootstrap class loader that Byte Buddy might
* require for instrumenting a class or to instrument any of Byte Buddy's classes. If such instrumentation is desired, it is important to
* assert for each class that they are not loaded during instrumentation.
*
*
* @param typeMatcher A matcher that identifies types that should not be instrumented.
* @param classLoaderMatcher A matcher that identifies a class loader that identifies classes that should not be instrumented.
* @return A new instance of this agent builder that ignores all types that are matched by the provided matcher.
* All previous matchers for ignored types are discarded.
*/
Ignored ignore(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher);
/**
*
* Excludes any type that is matched by the provided matcher and is loaded by a class loader matching the second matcher.
* By default, Byte Buddy does not instrument synthetic types, types within a {@code com.fitbur.mockito.bytebuddy.*} package or types that
* are loaded by the bootstrap class loader.
*
*
* When ignoring a type, any subsequently chained matcher is applied after this matcher in the order of their registration. Also, if
* any matcher indicates that a type is to be ignored, none of the following chained matchers is executed.
*
*
* Note: For performance reasons, it is recommended to always include a matcher that excludes as many namespaces
* as possible. Byte Buddy can determine a type's name without parsing its class file and can therefore discard such
* types with minimal overhead. When a different property of a type - such as for example its modifiers or its annotations
* is accessed - Byte Buddy parses the class file lazily in order to allow for such a matching. Therefore, any exclusion
* of a name should always be done as a first step and even if it does not influence the selection of what types are
* matched. Without changing this property, the class file of every type is being parsed!
*
*
* Warning: If a type is loaded during the instrumentation of the same type, this causes the original call site that loads the type
* to remain unbound, causing a {@link LinkageError}. It is therefore important to not instrument types that may be loaded during the application
* of a {@link Transformer}. For this reason, it is not recommended to instrument classes of the bootstrap class loader that Byte Buddy might
* require for instrumenting a class or to instrument any of Byte Buddy's classes. If such instrumentation is desired, it is important to
* assert for each class that they are not loaded during instrumentation.
*
*
* @param typeMatcher A matcher that identifies types that should not be instrumented.
* @param classLoaderMatcher A matcher that identifies a class loader that identifies classes that should not be instrumented.
* @param moduleMatcher A matcher that identifies a module that identifies classes that should not be instrumented. On a JVM
* that does not support the Java modules system, this matcher is not applied.
* @return A new instance of this agent builder that ignores all types that are matched by the provided matcher.
* All previous matchers for ignored types are discarded.
*/
Ignored ignore(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher);
/**
*
* Excludes any type that is matched by the raw matcher provided to this method. By default, Byte Buddy does not
* instrument synthetic types, types within a {@code com.fitbur.mockito.bytebuddy.*} package or types that are loaded by the bootstrap class loader.
*
*
* When ignoring a type, any subsequently chained matcher is applied after this matcher in the order of their registration. Also, if
* any matcher indicates that a type is to be ignored, none of the following chained matchers is executed.
*
*
* Note: For performance reasons, it is recommended to always include a matcher that excludes as many namespaces
* as possible. Byte Buddy can determine a type's name without parsing its class file and can therefore discard such
* types with minimal overhead. When a different property of a type - such as for example its modifiers or its annotations
* is accessed - Byte Buddy parses the class file lazily in order to allow for such a matching. Therefore, any exclusion
* of a name should always be done as a first step and even if it does not influence the selection of what types are
* matched. Without changing this property, the class file of every type is being parsed!
*
*
* Warning: If a type is loaded during the instrumentation of the same type, this causes the original call site that loads the type
* to remain unbound, causing a {@link LinkageError}. It is therefore important to not instrument types that may be loaded during the application
* of a {@link Transformer}. For this reason, it is not recommended to instrument classes of the bootstrap class loader that Byte Buddy might
* require for instrumenting a class or to instrument any of Byte Buddy's classes. If such instrumentation is desired, it is important to
* assert for each class that they are not loaded during instrumentation.
*
*
* @param rawMatcher A raw matcher that identifies types that should not be instrumented.
* @return A new instance of this agent builder that ignores all types that are matched by the provided matcher.
* All previous matchers for ignored types are discarded.
*/
Ignored ignore(RawMatcher rawMatcher);
/**
* Creates a {@link java.lang.instrument.ClassFileTransformer} that implements the configuration of this
* agent builder.
*
* @return A class file transformer that implements the configuration of this agent builder.
*/
ClassFileTransformer makeRaw();
/**
* Creates and installs a {@link java.lang.instrument.ClassFileTransformer} that implements the configuration of
* this agent builder with a given {@link java.lang.instrument.Instrumentation}. If retransformation is enabled,
* the installation also causes all loaded types to be retransformed.
*
* @param instrumentation The instrumentation on which this agent builder's configuration is to be installed.
* @return The installed class file transformer.
*/
ClassFileTransformer installOn(Instrumentation instrumentation);
/**
* Creates and installs a {@link java.lang.instrument.ClassFileTransformer} that implements the configuration of
* this agent builder with the Byte Buddy-agent which must be installed prior to calling this method.
*
* @return The installed class file transformer.
*/
ClassFileTransformer installOnByteBuddyAgent();
/**
* An abstraction for extending a matcher.
*
* @param The type that is produced by chaining a matcher.
*/
interface Matchable> {
/**
* Defines a matching that is positive if both the previous matcher and the supplied matcher are matched. When matching a
* type, class loaders are not considered.
*
* @param typeMatcher A matcher for the type being matched.
* @return A chained matcher.
*/
T and(ElementMatcher super TypeDescription> typeMatcher);
/**
* Defines a matching that is positive if both the previous matcher and the supplied matcher are matched.
*
* @param typeMatcher A matcher for the type being matched.
* @param classLoaderMatcher A matcher for the type's class loader.
* @return A chained matcher.
*/
T and(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher);
/**
* Defines a matching that is positive if both the previous matcher and the supplied matcher are matched.
*
* @param typeMatcher A matcher for the type being matched.
* @param classLoaderMatcher A matcher for the type's class loader.
* @param moduleMatcher A matcher for the type's module. On a JVM that does not support modules, the Java module is represented by {@code null}.
* @return A chained matcher.
*/
T and(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher);
/**
* Defines a matching that is positive if both the previous matcher and the supplied matcher are matched.
*
* @param rawMatcher A raw matcher for the type being matched.
* @return A chained matcher.
*/
T and(RawMatcher rawMatcher);
/**
* Defines a matching that is positive if the previous matcher or the supplied matcher are matched. When matching a
* type, the class loader is not considered.
*
* @param typeMatcher A matcher for the type being matched.
* @return A chained matcher.
*/
T or(ElementMatcher super TypeDescription> typeMatcher);
/**
* Defines a matching that is positive if the previous matcher or the supplied matcher are matched.
*
* @param typeMatcher A matcher for the type being matched.
* @param classLoaderMatcher A matcher for the type's class loader.
* @return A chained matcher.
*/
T or(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher);
/**
* Defines a matching that is positive if the previous matcher or the supplied matcher are matched.
*
* @param typeMatcher A matcher for the type being matched.
* @param classLoaderMatcher A matcher for the type's class loader.
* @param moduleMatcher A matcher for the type's module. On a JVM that does not support modules, the Java module is represented by {@code null}.
* @return A chained matcher.
*/
T or(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher);
/**
* Defines a matching that is positive if the previous matcher or the supplied matcher are matched.
*
* @param rawMatcher A raw matcher for the type being matched.
* @return A chained matcher.
*/
T or(RawMatcher rawMatcher);
/**
* An abstract base implementation of a matchable.
*
* @param The type that is produced by chaining a matcher.
*/
abstract class AbstractBase> implements Matchable {
@Override
public S and(ElementMatcher super TypeDescription> typeMatcher) {
return and(typeMatcher, any());
}
@Override
public S and(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher) {
return and(typeMatcher, classLoaderMatcher, any());
}
@Override
public S and(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher) {
return and(new RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, moduleMatcher));
}
@Override
public S or(ElementMatcher super TypeDescription> typeMatcher) {
return or(typeMatcher, any());
}
@Override
public S or(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher) {
return or(typeMatcher, classLoaderMatcher, any());
}
@Override
public S or(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher) {
return or(new RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, moduleMatcher));
}
}
}
/**
* Allows to further specify ignored types.
*/
interface Ignored extends Matchable, AgentBuilder {
/* this is merely a unionizing interface that does not declare methods */
}
/**
* Describes an {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder} which was handed a matcher for identifying
* types to instrumented in order to supply one or several
* {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s.
*/
interface Identified {
/**
* Applies the given transformer for the already supplied matcher.
*
* @param transformer The transformer to apply.
* @return A new instance of this agent builder with the transformer being applied when the previously supplied matcher
* identified a type for instrumentation which also allows for the registration of subsequent transformers.
*/
Extendable transform(Transformer transformer);
/**
* Allows to specify a type matcher for a type to instrument.
*/
interface Narrowable extends Matchable, Identified {
/* this is merely a unionizing interface that does not declare methods */
}
/**
* This interface is used to allow for optionally providing several
* {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer} to applied when a matcher identifies a type
* to be instrumented. Any subsequent transformers are applied in the order they are registered.
*/
interface Extendable extends AgentBuilder, Identified {
/**
*
* Applies the specified transformation as a decorative transformation. For a decorative transformation, the supplied
* transformer is prepended to any previous transformation that also matches the instrumented type, i.e. both transformations
* are supplied. This procedure is repeated until a transformer is reached that matches the instrumented type but is not
* defined as decorating after which no further transformations are considered. If all matching transformations are declared
* as decorating, all matching transformers are applied.
*
*
* Note: A decorating transformer is applied after previously registered transformers.
*
*
* @return A new instance of this agent builder with the specified transformation being applied as a decorator.
*/
AgentBuilder asDecorator();
}
}
/**
* A matcher that allows to determine if a {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}
* should be applied during the execution of a {@link java.lang.instrument.ClassFileTransformer} that was
* generated by an {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder}.
*/
interface RawMatcher {
/**
* Decides if the given {@code typeDescription} should be instrumented with the entailed
* {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s.
*
* @param typeDescription A description of the type to be instrumented.
* @param classLoader The class loader of the instrumented type. Might be {@code null} if this class
* loader represents the bootstrap class loader.
* @param module The transformed type's module or {@code null} if the current VM does not support modules.
* @param classBeingRedefined The class being redefined which is only not {@code null} if a retransformation
* is applied.
* @param protectionDomain The protection domain of the type being transformed.
* @return {@code true} if the entailed {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s should
* be applied for the given {@code typeDescription}.
*/
boolean matches(TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module,
Class> classBeingRedefined,
ProtectionDomain protectionDomain);
/**
* A conjunction of two raw matchers.
*/
class Conjunction implements RawMatcher {
/**
* The left matcher which is applied first.
*/
private final RawMatcher left;
/**
* The right matcher which is applied second.
*/
private final RawMatcher right;
/**
* Creates a new conjunction of two raw matchers.
*
* @param left The left matcher which is applied first.
* @param right The right matcher which is applied second.
*/
protected Conjunction(RawMatcher left, RawMatcher right) {
this.left = left;
this.right = right;
}
@Override
public boolean matches(TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module,
Class> classBeingRedefined,
ProtectionDomain protectionDomain) {
return left.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)
&& right.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain);
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
Conjunction that = (Conjunction) object;
return left.equals(that.left) && right.equals(that.right);
}
@Override
public int hashCode() {
int result = left.hashCode();
result = 31 * result + right.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.RawMatcher.Conjunction{" +
"left=" + left +
", right=" + right +
'}';
}
}
/**
* A disjunction of two raw matchers.
*/
class Disjunction implements RawMatcher {
/**
* The left matcher which is applied first.
*/
private final RawMatcher left;
/**
* The right matcher which is applied second.
*/
private final RawMatcher right;
/**
* Creates a new disjunction of two raw matchers.
*
* @param left The left matcher which is applied first.
* @param right The right matcher which is applied second.
*/
protected Disjunction(RawMatcher left, RawMatcher right) {
this.left = left;
this.right = right;
}
@Override
public boolean matches(TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module,
Class> classBeingRedefined,
ProtectionDomain protectionDomain) {
return left.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)
|| right.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain);
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
Disjunction that = (Disjunction) object;
return left.equals(that.left) && right.equals(that.right);
}
@Override
public int hashCode() {
int result = left.hashCode();
result = 31 * result + right.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.RawMatcher.Disjunction{" +
"left=" + left +
", right=" + right +
'}';
}
}
/**
* A raw matcher implementation that checks a {@link TypeDescription}
* and its {@link java.lang.ClassLoader} against two suitable matchers in order to determine if the matched
* type should be instrumented.
*/
class ForElementMatchers implements RawMatcher {
/**
* The type matcher to apply to a {@link TypeDescription}.
*/
private final ElementMatcher super TypeDescription> typeMatcher;
/**
* The class loader matcher to apply to a {@link java.lang.ClassLoader}.
*/
private final ElementMatcher super ClassLoader> classLoaderMatcher;
/**
* A module matcher to apply to a {@code java.lang.reflect.Module}.
*/
private final ElementMatcher super JavaModule> moduleMatcher;
/**
* Creates a new {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.RawMatcher} that only matches the
* supplied {@link TypeDescription} and its {@link java.lang.ClassLoader} against two matcher in order
* to decided if an instrumentation should be conducted.
*
* @param typeMatcher The type matcher to apply to a {@link TypeDescription}.
* @param classLoaderMatcher The class loader matcher to apply to a {@link java.lang.ClassLoader}.
* @param moduleMatcher A module matcher to apply to a {@code java.lang.reflect.Module}.
*/
public ForElementMatchers(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher) {
this.typeMatcher = typeMatcher;
this.classLoaderMatcher = classLoaderMatcher;
this.moduleMatcher = moduleMatcher;
}
@Override
public boolean matches(TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module,
Class> classBeingRedefined,
ProtectionDomain protectionDomain) {
return moduleMatcher.matches(module) && classLoaderMatcher.matches(classLoader) && typeMatcher.matches(typeDescription);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& classLoaderMatcher.equals(((ForElementMatchers) other).classLoaderMatcher)
&& moduleMatcher.equals(((ForElementMatchers) other).moduleMatcher)
&& typeMatcher.equals(((ForElementMatchers) other).typeMatcher);
}
@Override
public int hashCode() {
int result = typeMatcher.hashCode();
result = 31 * result + classLoaderMatcher.hashCode();
result = 31 * result + moduleMatcher.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.RawMatcher.ForElementMatchers{" +
"typeMatcher=" + typeMatcher +
", classLoaderMatcher=" + classLoaderMatcher +
", moduleMatcher=" + moduleMatcher +
'}';
}
}
}
/**
* A type strategy is responsible for creating a type builder for a type that is being instrumented.
*/
interface TypeStrategy {
/**
* Creates a type builder for a given type.
*
* @param typeDescription The type being instrumented.
* @param byteBuddy The Byte Buddy configuration.
* @param classFileLocator The class file locator to use.
* @param methodNameTransformer The method name transformer to use.
* @return A type builder for the given arguments.
*/
DynamicType.Builder> builder(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer);
/**
* Default implementations of type strategies.
*/
enum Default implements TypeStrategy {
/**
* A definition handler that performs a rebasing for all types.
*/
REBASE {
@Override
public DynamicType.Builder> builder(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer) {
return byteBuddy.rebase(typeDescription, classFileLocator, methodNameTransformer);
}
},
/**
*
* A definition handler that performs a redefinition for all types.
*
*
* Note that the default agent builder is configured to apply a self initialization where a static class initializer
* is added to the redefined class. This can be disabled by for example using a {@link InitializationStrategy.Minimal} or
* {@link InitializationStrategy.NoOp}. Also, consider the constraints implied by {@link ByteBuddy#redefine(TypeDescription, ClassFileLocator)}.
*
*
* For prohibiting any changes on a class file, use {@link AgentBuilder#disableClassFormatChanges()}
*
*/
REDEFINE {
@Override
public DynamicType.Builder> builder(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer) {
return byteBuddy.redefine(typeDescription, classFileLocator);
}
},
/**
*
* A definition handler that performs a redefinition for all types and ignores all methods that were not declared by the instrumented type.
*
*
* Note that the default agent builder is configured to apply a self initialization where a static class initializer
* is added to the redefined class. This can be disabled by for example using a {@link InitializationStrategy.Minimal} or
* {@link InitializationStrategy.NoOp}. Also, consider the constraints implied by {@link ByteBuddy#redefine(TypeDescription, ClassFileLocator)}.
*
*
* For prohibiting any changes on a class file, use {@link AgentBuilder#disableClassFormatChanges()}
*
*/
REDEFINE_DECLARED_ONLY {
@Override
public DynamicType.Builder> builder(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer) {
return byteBuddy.redefine(typeDescription, classFileLocator).ignoreAlso(not(isDeclaredBy(typeDescription)));
}
};
@Override
public String toString() {
return "AgentBuilder.TypeStrategy.Default." + name();
}
}
}
/**
* A transformer allows to apply modifications to a {@link com.fitbur.mockito.bytebuddy.dynamic.DynamicType}. Such a modification
* is then applied to any instrumented type that was matched by the preceding matcher.
*/
interface Transformer {
/**
* Allows for a transformation of a {@link com.fitbur.mockito.bytebuddy.dynamic.DynamicType.Builder}.
*
* @param builder The dynamic builder to transform.
* @param typeDescription The description of the type currently being instrumented.
* @param classLoader The class loader of the instrumented class. Might be {@code null} to
* represent the bootstrap class loader.
* @return A transformed version of the supplied {@code builder}.
*/
DynamicType.Builder> transform(DynamicType.Builder> builder, TypeDescription typeDescription, ClassLoader classLoader);
/**
* A no-op implementation of a {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer} that does
* not modify the supplied dynamic type.
*/
enum NoOp implements Transformer {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public DynamicType.Builder> transform(DynamicType.Builder> builder, TypeDescription typeDescription, ClassLoader classLoader) {
return builder;
}
@Override
public String toString() {
return "AgentBuilder.Transformer.NoOp." + name();
}
}
/**
* A compound transformer that allows to group several
* {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s as a single transformer.
*/
class Compound implements Transformer {
/**
* The transformers to apply in their application order.
*/
private final Transformer[] transformer;
/**
* Creates a new compound transformer.
*
* @param transformer The transformers to apply in their application order.
*/
public Compound(Transformer... transformer) {
this.transformer = transformer;
}
@Override
public DynamicType.Builder> transform(DynamicType.Builder> builder, TypeDescription typeDescription, ClassLoader classLoader) {
for (Transformer transformer : this.transformer) {
builder = transformer.transform(builder, typeDescription, classLoader);
}
return builder;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& Arrays.equals(transformer, ((Compound) other).transformer);
}
@Override
public int hashCode() {
return Arrays.hashCode(transformer);
}
@Override
public String toString() {
return "AgentBuilder.Transformer.Compound{" +
"transformer=" + Arrays.toString(transformer) +
'}';
}
}
}
/**
* A type locator allows to specify how {@link TypeDescription}s are resolved by an {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder}.
*/
interface TypeLocator {
/**
* Creates a type pool for a given class file locator.
*
* @param classFileLocator The class file locator to use.
* @param classLoader The class loader for which the class file locator was created.
* @return A type pool for the supplied class file locator.
*/
TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader);
/**
* A default implementation of a {@link TypeLocator} that
* is using a {@link com.fitbur.mockito.bytebuddy.pool.TypePool.Default} with a
* {@link com.fitbur.mockito.bytebuddy.pool.TypePool.CacheProvider.Simple} and a
* {@link com.fitbur.mockito.bytebuddy.dynamic.ClassFileLocator.ForClassLoader}.
*/
enum Default implements TypeLocator {
/**
* A type locator that parses the code segment of each method for extracting information about parameter
* names even if they are not explicitly included in a class file.
*
* @see com.fitbur.mockito.bytebuddy.pool.TypePool.Default.ReaderMode#EXTENDED
*/
EXTENDED(TypePool.Default.ReaderMode.EXTENDED),
/**
* A type locator that skips the code segment of each method and does therefore not extract information
* about parameter names. Parameter names are still included if they are explicitly included in a class file.
*
* @see com.fitbur.mockito.bytebuddy.pool.TypePool.Default.ReaderMode#FAST
*/
FAST(TypePool.Default.ReaderMode.FAST);
/**
* The reader mode to apply by this type locator.
*/
private final TypePool.Default.ReaderMode readerMode;
/**
* Creates a new type locator.
*
* @param readerMode The reader mode to apply by this type locator.
*/
Default(TypePool.Default.ReaderMode readerMode) {
this.readerMode = readerMode;
}
@Override
public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) {
return new TypePool.LazyFacade(TypePool.Default.Precomputed.withObjectType(new TypePool.CacheProvider.Simple(), classFileLocator, readerMode));
}
@Override
public String toString() {
return "AgentBuilder.TypeLocator.Default." + name();
}
}
/**
*
* A type locator that loads referenced classes instead of describing unloaded versions.
*
*
* Important: It is important to never query this type locator for the currently instrumented type as this will yield a class
* loading circularity which aborts any instrumentation with an error.
*
*
* Warning: Warning, this type locator cannot be used for applying a redefinition as it works on loaded classes only and
* is agnostic of any way to locate a class file.
*
*/
enum ClassLoading implements TypeLocator {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) {
return new TypePool.LazyFacade(TypePool.Default.ClassLoading.of(classFileLocator, classLoader));
}
@Override
public String toString() {
return "AgentBuilder.TypeLocator.ClassLoading." + name();
}
}
/**
* A type locator that uses type pools but allows for the configuration of a custom cache provider by class loader. Note that a
* {@link TypePool} can grow in size and that a static reference is kept to this pool by Byte Buddy's registration of a
* {@link ClassFileTransformer} what can cause a memory leak if the supplied caches are not cleared on a regular basis. Also note
* that a cache provider can be accessed concurrently by multiple {@link ClassLoader}s.
*/
abstract class WithTypePoolCache implements TypeLocator {
/**
* The reader mode to use for parsing a class file.
*/
protected final TypePool.Default.ReaderMode readerMode;
/**
* Creates a new type locator that creates {@link TypePool}s but provides a custom {@link com.fitbur.mockito.bytebuddy.pool.TypePool.CacheProvider}.
*
* @param readerMode The reader mode to use for parsing a class file.
*/
protected WithTypePoolCache(TypePool.Default.ReaderMode readerMode) {
this.readerMode = readerMode;
}
@Override
public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) {
return new TypePool.LazyFacade(TypePool.Default.Precomputed.withObjectType(locate(classLoader), classFileLocator, readerMode));
}
/**
* Locates a cache provider for a given class loader.
*
* @param classLoader The class loader for which to locate a cache. This class loader might be {@code null} to represent the bootstrap loader.
* @return The cache provider to use.
*/
protected abstract TypePool.CacheProvider locate(ClassLoader classLoader);
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
WithTypePoolCache that = (WithTypePoolCache) object;
return readerMode == that.readerMode;
}
@Override
public int hashCode() {
return readerMode.hashCode();
}
/**
* An implementation of a type locator {@link WithTypePoolCache} (note documentation of the linked class) that is based on a
* {@link ConcurrentMap}. It is the responsibility of the type locator's user to avoid the type locator from leaking memory.
*/
public static class Simple extends WithTypePoolCache {
/**
* The concurrent map that is used for storing a cache provider per class loader.
*/
private final ConcurrentMap super ClassLoader, TypePool.CacheProvider> cacheProviders;
/**
* Creates a new type locator that caches a cache provider per class loader in a concurrent map. The type
* locator uses a fast {@link com.fitbur.mockito.bytebuddy.pool.TypePool.Default.ReaderMode}.
*
* @param cacheProviders The concurrent map that is used for storing a cache provider per class loader.
*/
public Simple(ConcurrentMap super ClassLoader, TypePool.CacheProvider> cacheProviders) {
this(TypePool.Default.ReaderMode.FAST, cacheProviders);
}
/**
* Creates a new type locator that caches a cache provider per class loader in a concurrent map.
*
* @param readerMode The reader mode to use for parsing a class file.
* @param cacheProviders The concurrent map that is used for storing a cache provider per class loader.
*/
public Simple(TypePool.Default.ReaderMode readerMode, ConcurrentMap super ClassLoader, TypePool.CacheProvider> cacheProviders) {
super(readerMode);
this.cacheProviders = cacheProviders;
}
@Override
protected TypePool.CacheProvider locate(ClassLoader classLoader) {
classLoader = classLoader == null ? BootstrapClassLoaderMarker.INSTANCE : classLoader;
TypePool.CacheProvider cacheProvider = cacheProviders.get(classLoader);
while (cacheProvider == null) {
cacheProviders.putIfAbsent(classLoader, new TypePool.CacheProvider.Simple());
cacheProvider = cacheProviders.get(classLoader);
}
return cacheProvider;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
if (!super.equals(object)) return false;
Simple simple = (Simple) object;
return cacheProviders.equals(simple.cacheProviders);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + cacheProviders.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.TypeLocator.WithTypePoolCache.Simple{" +
"cacheProviders=" + cacheProviders +
'}';
}
/**
* A marker for the bootstrap class loader which is represented by {@code null}.
*/
private static class BootstrapClassLoaderMarker extends ClassLoader {
/**
* A static reference to the a singleton instance of the marker to preserve reference equality.
*/
protected static final ClassLoader INSTANCE = AccessController.doPrivileged(new CreationAction());
@Override
protected Class> loadClass(String name, boolean resolve) {
throw new UnsupportedOperationException("This loader is only a non-null marker and is not supposed to be used");
}
/**
* A simple action for creating a bootstrap class loader marker.
*/
private static class CreationAction implements PrivilegedAction {
@Override
public ClassLoader run() {
return new BootstrapClassLoaderMarker();
}
}
}
}
}
}
/**
* A listener that is informed about events that occur during an instrumentation process.
*/
interface Listener {
/**
* Invoked right before a successful transformation is applied.
*
* @param typeDescription The type that is being transformed.
* @param classLoader The class loader which is loading this type.
* @param module The transformed type's module or {@code null} if the current VM does not support modules.
* @param dynamicType The dynamic type that was created.
*/
void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, DynamicType dynamicType);
/**
* Invoked when a type is not transformed but ignored.
*
* @param typeDescription The type being ignored for transformation.
* @param classLoader The class loader which is loading this type.
* @param module The ignored type's module or {@code null} if the current VM does not support modules.
*/
void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module);
/**
* Invoked when an error has occurred during transformation.
*
* @param typeName The type name of the instrumented type.
* @param classLoader The class loader which is loading this type.
* @param module The instrumented type's module or {@code null} if the current VM does not support modules.
* @param throwable The occurred error.
*/
void onError(String typeName, ClassLoader classLoader, JavaModule module, Throwable throwable);
/**
* Invoked after a class was attempted to be loaded, independently of its treatment.
*
* @param typeName The binary name of the instrumented type.
* @param classLoader The class loader which is loading this type.
* @param module The instrumented type's module or {@code null} if the current VM does not support modules.
*/
void onComplete(String typeName, ClassLoader classLoader, JavaModule module);
/**
* A no-op implementation of a {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Listener}.
*/
enum NoOp implements Listener {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, DynamicType dynamicType) {
/* do nothing */
}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
/* do nothing */
}
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, Throwable throwable) {
/* do nothing */
}
@Override
public void onComplete(String typeName, ClassLoader classLoader, JavaModule module) {
/* do nothing */
}
@Override
public String toString() {
return "AgentBuilder.Listener.NoOp." + name();
}
}
/**
* An adapter for a listener wher all methods are implemented as non-operational.
*/
abstract class Adapter implements Listener {
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, DynamicType dynamicType) {
/* do nothing */
}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
/* do nothing */
}
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, Throwable throwable) {
/* do nothing */
}
@Override
public void onComplete(String typeName, ClassLoader classLoader, JavaModule module) {
/* do nothing */
}
}
/**
* A listener that writes events to a {@link PrintStream}. This listener prints a line per event, including the event type and
* the name of the type in question.
*/
class StreamWriting implements Listener {
/**
* The prefix that is appended to all written messages.
*/
protected static final String PREFIX = "[Byte Buddy]";
/**
* The print stream written to.
*/
private final PrintStream printStream;
/**
* Creates a new stream writing listener.
*
* @param printStream The print stream written to.
*/
public StreamWriting(PrintStream printStream) {
this.printStream = printStream;
}
/**
* Creates a new stream writing listener that writes to {@link System#out}.
*
* @return A listener writing events to the standard output stream.
*/
public static Listener toSystemOut() {
return new StreamWriting(System.out);
}
/**
* Creates a new stream writing listener that writes to {@link System#err}.
*
* @return A listener writing events to the standad error stream.
*/
public static Listener toSystemError() {
return new StreamWriting(System.err);
}
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, DynamicType dynamicType) {
printStream.println(PREFIX + " TRANSFORM " + typeDescription.getName() + "[" + classLoader + ", " + module + "]");
}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
printStream.println(PREFIX + " IGNORE " + typeDescription.getName() + "[" + classLoader + ", " + module + "]");
}
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, Throwable throwable) {
printStream.println(PREFIX + " ERROR " + typeName + "[" + classLoader + ", " + module + "]");
throwable.printStackTrace(printStream);
}
@Override
public void onComplete(String typeName, ClassLoader classLoader, JavaModule module) {
printStream.println(PREFIX + " COMPLETE " + typeName + "[" + classLoader + ", " + module + "]");
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& printStream.equals(((StreamWriting) other).printStream);
}
@Override
public int hashCode() {
return printStream.hashCode();
}
@Override
public String toString() {
return "AgentBuilder.Listener.StreamWriting{" +
"printStream=" + printStream +
'}';
}
}
/**
* A listener that adds read-edges to any module of an instrumented class upon its transformation.
*/
class ModuleReadEdgeCompleting extends Listener.Adapter {
/**
* The instrumentation instance used for adding read edges.
*/
private final Instrumentation instrumentation;
/**
* {@code true} if the listener should also add a read-edge from the supplied modules to the instrumented type's module.
*/
private final boolean addTargetEdge;
/**
* The modules to add as a read edge to any transformed class's module.
*/
private final Set extends JavaModule> modules;
/**
* Creates a new module read-edge completing listener.
*
* @param instrumentation The instrumentation instance used for adding read edges.
* @param addTargetEdge {@code true} if the listener should also add a read-edge from the supplied modules
* to the instrumented type's module.
* @param modules The modules to add as a read edge to any transformed class's module.
*/
public ModuleReadEdgeCompleting(Instrumentation instrumentation, boolean addTargetEdge, Set extends JavaModule> modules) {
this.instrumentation = instrumentation;
this.addTargetEdge = addTargetEdge;
this.modules = modules;
}
/**
* Resolves a listener that adds module edges from and to the instrumented type's module.
*
* @param instrumentation The instrumentation instance used for adding read edges.
* @param addTargetEdge {@code true} if the listener should also add a read-edge from the supplied
* modules to the instrumented type's module.
* @param type The types for which to extract the modules.
* @return An appropriate listener.
*/
protected static Listener of(Instrumentation instrumentation, boolean addTargetEdge, Class>... type) {
Set modules = new HashSet();
for (Class> aType : type) {
JavaModule module = JavaModule.ofType(aType);
if (module.isNamed()) {
modules.add(module);
}
}
return modules.isEmpty()
? Listener.NoOp.INSTANCE
: new Listener.ModuleReadEdgeCompleting(instrumentation, addTargetEdge, modules);
}
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, DynamicType dynamicType) {
if (module != null && module.isNamed()) {
for (JavaModule target : modules) {
if (!module.canRead(target)) {
module.addReads(instrumentation, target);
}
if (addTargetEdge && !target.canRead(module)) {
target.addReads(instrumentation, module);
}
}
}
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
ModuleReadEdgeCompleting that = (ModuleReadEdgeCompleting) object;
return instrumentation.equals(that.instrumentation)
&& addTargetEdge == that.addTargetEdge
&& modules.equals(that.modules);
}
@Override
public int hashCode() {
int result = instrumentation.hashCode();
result = 31 * result + modules.hashCode();
result = 31 * result + (addTargetEdge ? 1 : 0);
return result;
}
@Override
public String toString() {
return "AgentBuilder.Listener.ModuleReadEdgeCompleting{" +
"instrumentation=" + instrumentation +
", addTargetEdge=" + addTargetEdge +
", modules=" + modules +
'}';
}
}
/**
* A compound listener that allows to group several listeners in one instance.
*/
class Compound implements Listener {
/**
* The listeners that are represented by this compound listener in their application order.
*/
private final List extends Listener> listeners;
/**
* Creates a new compound listener.
*
* @param listener The listeners to apply in their application order.
*/
public Compound(Listener... listener) {
this(Arrays.asList(listener));
}
/**
* Creates a new compound listener.
*
* @param listeners The listeners to apply in their application order.
*/
public Compound(List extends Listener> listeners) {
this.listeners = listeners;
}
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, DynamicType dynamicType) {
for (Listener listener : listeners) {
listener.onTransformation(typeDescription, classLoader, module, dynamicType);
}
}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
for (Listener listener : listeners) {
listener.onIgnored(typeDescription, classLoader, module);
}
}
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, Throwable throwable) {
for (Listener listener : listeners) {
listener.onError(typeName, classLoader, module, throwable);
}
}
@Override
public void onComplete(String typeName, ClassLoader classLoader, JavaModule module) {
for (Listener listener : listeners) {
listener.onComplete(typeName, classLoader, module);
}
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& listeners.equals(((Compound) other).listeners);
}
@Override
public int hashCode() {
return listeners.hashCode();
}
@Override
public String toString() {
return "AgentBuilder.Listener.Compound{" +
"listeners=" + listeners +
'}';
}
}
}
/**
* An initialization strategy which determines the handling of {@link com.fitbur.mockito.bytebuddy.implementation.LoadedTypeInitializer}s
* and the loading of auxiliary types.
*/
interface InitializationStrategy {
/**
* Creates a new dispatcher for injecting this initialization strategy during a transformation process.
*
* @return The dispatcher to be used.
*/
Dispatcher dispatcher();
/**
* A dispatcher for changing a class file to adapt a self-initialization strategy.
*/
interface Dispatcher {
/**
* Transforms the instrumented type to implement an appropriate initialization strategy.
*
* @param builder The builder which should implement the initialization strategy.
* @return The given {@code builder} with the initialization strategy applied.
*/
DynamicType.Builder> apply(DynamicType.Builder> builder);
/**
* Registers a dynamic type for initialization and/or begins the initialization process.
*
* @param dynamicType The dynamic type that is created.
* @param classLoader The class loader of the dynamic type.
* @param injectorFactory The injector factory
*/
void register(DynamicType dynamicType, ClassLoader classLoader, InjectorFactory injectorFactory);
/**
* A factory for creating a {@link ClassInjector} only if it is required.
*/
interface InjectorFactory {
/**
* Resolves the class injector for this factory.
*
* @return The class injector for this factory.
*/
ClassInjector resolve();
}
}
/**
* A non-initializing initialization strategy.
*/
enum NoOp implements InitializationStrategy, Dispatcher {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public Dispatcher dispatcher() {
return this;
}
@Override
public DynamicType.Builder> apply(DynamicType.Builder> builder) {
return builder;
}
@Override
public void register(DynamicType dynamicType, ClassLoader classLoader, InjectorFactory injectorFactory) {
/* do nothing */
}
@Override
public String toString() {
return "AgentBuilder.InitializationStrategy.NoOp." + name();
}
}
/**
* An initialization strategy that adds a code block to an instrumented type's type initializer which
* then calls a specific class that is responsible for the explicit initialization.
*/
@SuppressFBWarnings(value = "DMI_RANDOM_USED_ONLY_ONCE", justification = "Avoiding synchronization without security concerns")
enum SelfInjection implements InitializationStrategy {
/**
* A form of self-injection where auxiliary types that are annotated by
* {@link com.fitbur.mockito.bytebuddy.implementation.auxiliary.AuxiliaryType.SignatureRelevant} of the instrumented type are loaded lazily and
* any other auxiliary type is loaded eagerly.
*/
SPLIT {
@Override
public InitializationStrategy.Dispatcher dispatcher() {
return new SelfInjection.Dispatcher.Split(new Random().nextInt());
}
},
/**
* A form of self-injection where any auxiliary type is loaded lazily.
*/
LAZY {
@Override
public InitializationStrategy.Dispatcher dispatcher() {
return new SelfInjection.Dispatcher.Lazy(new Random().nextInt());
}
},
/**
* A form of self-injection where any auxiliary type is loaded eagerly.
*/
EAGER {
@Override
public InitializationStrategy.Dispatcher dispatcher() {
return new SelfInjection.Dispatcher.Eager(new Random().nextInt());
}
};
@Override
public String toString() {
return "AgentBuilder.InitializationStrategy.SelfInjection." + name();
}
/**
* A dispatcher for a self-initialization strategy.
*/
protected abstract static class Dispatcher implements InitializationStrategy.Dispatcher {
/**
* A random identification for the applied self-initialization.
*/
protected final int identification;
/**
* Creates a new dispatcher.
*
* @param identification A random identification for the applied self-initialization.
*/
protected Dispatcher(int identification) {
this.identification = identification;
}
@Override
public DynamicType.Builder> apply(DynamicType.Builder> builder) {
return builder.initializer(NexusAccessor.INSTANCE.identifiedBy(identification));
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& identification == ((Dispatcher) other).identification;
}
@Override
public int hashCode() {
return identification;
}
/**
* A dispatcher for the {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.InitializationStrategy.SelfInjection#SPLIT} strategy.
*/
protected static class Split extends Dispatcher {
/**
* Creates a new split dispatcher.
*
* @param identification A random identification for the applied self-initialization.
*/
protected Split(int identification) {
super(identification);
}
@Override
public void register(DynamicType dynamicType, ClassLoader classLoader, InjectorFactory injectorFactory) {
Map auxiliaryTypes = dynamicType.getAuxiliaryTypes();
LoadedTypeInitializer loadedTypeInitializer;
if (!auxiliaryTypes.isEmpty()) {
TypeDescription instrumentedType = dynamicType.getTypeDescription();
ClassInjector classInjector = injectorFactory.resolve();
Map independentTypes = new LinkedHashMap(auxiliaryTypes);
Map dependentTypes = new LinkedHashMap(auxiliaryTypes);
for (TypeDescription auxiliaryType : auxiliaryTypes.keySet()) {
(auxiliaryType.getDeclaredAnnotations().isAnnotationPresent(AuxiliaryType.SignatureRelevant.class)
? dependentTypes
: independentTypes).remove(auxiliaryType);
}
Map loadedTypeInitializers = dynamicType.getLoadedTypeInitializers();
if (!independentTypes.isEmpty()) {
for (Map.Entry> entry : classInjector.inject(independentTypes).entrySet()) {
loadedTypeInitializers.get(entry.getKey()).onLoad(entry.getValue());
}
}
Map lazyInitializers = new HashMap(loadedTypeInitializers);
loadedTypeInitializers.keySet().removeAll(independentTypes.keySet());
loadedTypeInitializer = lazyInitializers.size() > 1 // there exist auxiliary types that need lazy loading
? new InjectingInitializer(instrumentedType, dependentTypes, lazyInitializers, classInjector)
: lazyInitializers.get(instrumentedType);
} else {
loadedTypeInitializer = dynamicType.getLoadedTypeInitializers().get(dynamicType.getTypeDescription());
}
if (loadedTypeInitializer.isAlive()) {
NexusAccessor.INSTANCE.register(dynamicType.getTypeDescription().getName(), classLoader, identification, loadedTypeInitializer);
}
}
@Override
public String toString() {
return "AgentBuilder.InitializationStrategy.SelfInjection.Dispatcher.Split{identification=" + identification + "}";
}
}
/**
* A dispatcher for the {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.InitializationStrategy.SelfInjection#LAZY} strategy.
*/
protected static class Lazy extends Dispatcher {
/**
* Creates a new lazy dispatcher.
*
* @param identification A random identification for the applied self-initialization.
*/
protected Lazy(int identification) {
super(identification);
}
@Override
public void register(DynamicType dynamicType, ClassLoader classLoader, InjectorFactory injectorFactory) {
Map auxiliaryTypes = dynamicType.getAuxiliaryTypes();
LoadedTypeInitializer loadedTypeInitializer = auxiliaryTypes.isEmpty()
? dynamicType.getLoadedTypeInitializers().get(dynamicType.getTypeDescription())
: new InjectingInitializer(dynamicType.getTypeDescription(), auxiliaryTypes, dynamicType.getLoadedTypeInitializers(), injectorFactory.resolve());
if (loadedTypeInitializer.isAlive()) {
NexusAccessor.INSTANCE.register(dynamicType.getTypeDescription().getName(), classLoader, identification, loadedTypeInitializer);
}
}
@Override
public String toString() {
return "AgentBuilder.InitializationStrategy.SelfInjection.Dispatcher.Lazy{identification=" + identification + "}";
}
}
/**
* A dispatcher for the {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.InitializationStrategy.SelfInjection#EAGER} strategy.
*/
protected static class Eager extends Dispatcher {
/**
* Creates a new eager dispatcher.
*
* @param identification A random identification for the applied self-initialization.
*/
protected Eager(int identification) {
super(identification);
}
@Override
public void register(DynamicType dynamicType, ClassLoader classLoader, InjectorFactory injectorFactory) {
Map auxiliaryTypes = dynamicType.getAuxiliaryTypes();
Map loadedTypeInitializers = dynamicType.getLoadedTypeInitializers();
if (!auxiliaryTypes.isEmpty()) {
for (Map.Entry> entry : injectorFactory.resolve().inject(auxiliaryTypes).entrySet()) {
loadedTypeInitializers.get(entry.getKey()).onLoad(entry.getValue());
}
}
LoadedTypeInitializer loadedTypeInitializer = loadedTypeInitializers.get(dynamicType.getTypeDescription());
if (loadedTypeInitializer.isAlive()) {
NexusAccessor.INSTANCE.register(dynamicType.getTypeDescription().getName(), classLoader, identification, loadedTypeInitializer);
}
}
@Override
public String toString() {
return "AgentBuilder.InitializationStrategy.SelfInjection.Dispatcher.Eager{identification=" + identification + "}";
}
}
/**
* A type initializer that injects all auxiliary types of the instrumented type.
*/
protected static class InjectingInitializer implements LoadedTypeInitializer {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The auxiliary types mapped to their class file representation.
*/
private final Map rawAuxiliaryTypes;
/**
* The instrumented types and auxiliary types mapped to their loaded type initializers.
* The instrumented types and auxiliary types mapped to their loaded type initializers.
*/
private final Map loadedTypeInitializers;
/**
* The class injector to use.
*/
private final ClassInjector classInjector;
/**
* Creates a new injection initializer.
*
* @param instrumentedType The instrumented type.
* @param rawAuxiliaryTypes The auxiliary types mapped to their class file representation.
* @param loadedTypeInitializers The instrumented types and auxiliary types mapped to their loaded type initializers.
* @param classInjector The class injector to use.
*/
protected InjectingInitializer(TypeDescription instrumentedType,
Map rawAuxiliaryTypes,
Map loadedTypeInitializers,
ClassInjector classInjector) {
this.instrumentedType = instrumentedType;
this.rawAuxiliaryTypes = rawAuxiliaryTypes;
this.loadedTypeInitializers = loadedTypeInitializers;
this.classInjector = classInjector;
}
@Override
public void onLoad(Class> type) {
for (Map.Entry> auxiliary : classInjector.inject(rawAuxiliaryTypes).entrySet()) {
loadedTypeInitializers.get(auxiliary.getKey()).onLoad(auxiliary.getValue());
}
loadedTypeInitializers.get(instrumentedType).onLoad(type);
}
@Override
public boolean isAlive() {
return true;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InjectingInitializer that = (InjectingInitializer) o;
return classInjector.equals(that.classInjector)
&& instrumentedType.equals(that.instrumentedType)
&& rawAuxiliaryTypes.equals(that.rawAuxiliaryTypes)
&& loadedTypeInitializers.equals(that.loadedTypeInitializers);
}
@Override
public int hashCode() {
int result = instrumentedType.hashCode();
result = 31 * result + rawAuxiliaryTypes.hashCode();
result = 31 * result + loadedTypeInitializers.hashCode();
result = 31 * result + classInjector.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.InitializationStrategy.SelfInjection.Dispatcher.InjectingInitializer{" +
"instrumentedType=" + instrumentedType +
", rawAuxiliaryTypes=" + rawAuxiliaryTypes +
", loadedTypeInitializers=" + loadedTypeInitializers +
", classInjector=" + classInjector +
'}';
}
}
}
/**
* An accessor for making sure that the accessed {@link com.fitbur.mockito.bytebuddy.agent.builder.Nexus} is the class that is loaded by the system class loader.
*/
protected enum NexusAccessor {
/**
* The singleton instance.
*/
INSTANCE;
/**
* The dispatcher for registering type initializers in the {@link Nexus}.
*/
private final Dispatcher dispatcher;
/**
* The {@link ClassLoader#getSystemClassLoader()} method.
*/
private final MethodDescription.InDefinedShape getSystemClassLoader;
/**
* The {@link java.lang.ClassLoader#loadClass(String)} method.
*/
private final MethodDescription.InDefinedShape loadClass;
/**
* The {@link Integer#valueOf(int)} method.
*/
private final MethodDescription.InDefinedShape valueOf;
/**
* The {@link java.lang.Class#getDeclaredMethod(String, Class[])} method.
*/
private final MethodDescription getDeclaredMethod;
/**
* The {@link java.lang.reflect.Method#invoke(Object, Object...)} method.
*/
private final MethodDescription invokeMethod;
/**
* Creates the singleton accessor.
*/
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Explicit delegation of the exception")
NexusAccessor() {
Dispatcher dispatcher;
try {
TypeDescription nexusType = new TypeDescription.ForLoadedType(Nexus.class);
dispatcher = new Dispatcher.Available(ClassInjector.UsingReflection.ofSystemClassLoader()
.inject(Collections.singletonMap(nexusType, ClassFileLocator.ForClassLoader.read(Nexus.class).resolve()))
.get(nexusType)
.getDeclaredMethod("register", String.class, ClassLoader.class, int.class, Object.class));
} catch (Exception exception) {
try {
dispatcher = new Dispatcher.Available(ClassLoader.getSystemClassLoader()
.loadClass(Nexus.class.getName())
.getDeclaredMethod("register", String.class, ClassLoader.class, int.class, Object.class));
} catch (Exception ignored) {
dispatcher = new Dispatcher.Unavailable(exception);
}
}
this.dispatcher = dispatcher;
getSystemClassLoader = new TypeDescription.ForLoadedType(ClassLoader.class).getDeclaredMethods()
.filter(named("getSystemClassLoader").and(takesArguments(0))).getOnly();
loadClass = new TypeDescription.ForLoadedType(ClassLoader.class).getDeclaredMethods()
.filter(named("loadClass").and(takesArguments(String.class))).getOnly();
getDeclaredMethod = new TypeDescription.ForLoadedType(Class.class).getDeclaredMethods()
.filter(named("getDeclaredMethod").and(takesArguments(String.class, Class[].class))).getOnly();
invokeMethod = new TypeDescription.ForLoadedType(Method.class).getDeclaredMethods()
.filter(named("invoke").and(takesArguments(Object.class, Object[].class))).getOnly();
valueOf = new TypeDescription.ForLoadedType(Integer.class).getDeclaredMethods()
.filter(named("valueOf").and(takesArguments(int.class))).getOnly();
}
/**
* Registers a type initializer with the class loader's nexus.
*
* @param name The name of a type for which a loaded type initializer is registered.
* @param classLoader The class loader for which a loaded type initializer is registered.
* @param identification An identification for the initializer to run.
* @param typeInitializer The loaded type initializer to be registered.
*/
public void register(String name, ClassLoader classLoader, int identification, LoadedTypeInitializer typeInitializer) {
dispatcher.register(name, classLoader, identification, typeInitializer);
}
/**
* Creates a byte code appender for injecting a self-initializing type initializer block into the generated class.
*
* @param identification The identification of the initialization.
* @return An appropriate byte code appender.
*/
public ByteCodeAppender identifiedBy(int identification) {
return new InitializationAppender(identification);
}
@Override
public String toString() {
return "AgentBuilder.InitializationStrategy.SelfInjection.NexusAccessor." + name();
}
/**
* A dispatcher for registering type initializers in the {@link Nexus}.
*/
protected interface Dispatcher {
/**
* Registers a type initializer with the class loader's nexus.
*
* @param name The name of a type for which a loaded type initializer is registered.
* @param classLoader The class loader for which a loaded type initializer is registered.
* @param identification An identification for the initializer to run.
* @param typeInitializer The loaded type initializer to be registered.
*/
void register(String name, ClassLoader classLoader, int identification, LoadedTypeInitializer typeInitializer);
/**
* An enabled dispatcher for registering a type initializer in a {@link Nexus}.
*/
class Available implements Dispatcher {
/**
* Indicates that a static method is invoked by reflection.
*/
private static final Object STATIC_METHOD = null;
/**
* The method for registering a type initializer in the system class loader's {@link Nexus}.
*/
private final Method registration;
/**
* Creates a new dispatcher.
*
* @param registration The method for registering a type initializer in the system class loader's {@link Nexus}.
*/
protected Available(Method registration) {
this.registration = registration;
}
@Override
public void register(String name, ClassLoader classLoader, int identification, LoadedTypeInitializer typeInitializer) {
try {
registration.invoke(STATIC_METHOD, name, classLoader, identification, typeInitializer);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Cannot register type initializer for " + name, exception);
} catch (InvocationTargetException exception) {
throw new IllegalStateException("Cannot register type initializer for " + name, exception.getCause());
}
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& registration.equals(((Available) other).registration);
}
@Override
public int hashCode() {
return registration.hashCode();
}
@Override
public String toString() {
return "AgentBuilder.InitializationStrategy.SelfInjection.NexusAccessor.Dispatcher.Available{" +
"registration=" + registration +
'}';
}
}
/**
* A disabled dispatcher where a {@link Nexus} is not available.
*/
class Unavailable implements Dispatcher {
/**
* The exception that was raised during the dispatcher initialization.
*/
private final Exception exception;
/**
* Creates a new disabled dispatcher.
*
* @param exception The exception that was raised during the dispatcher initialization.
*/
protected Unavailable(Exception exception) {
this.exception = exception;
}
@Override
public void register(String name, ClassLoader classLoader, int identification, LoadedTypeInitializer typeInitializer) {
throw new IllegalStateException("Could not locate registration method", exception);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& exception.equals(((Unavailable) other).exception);
}
@Override
public int hashCode() {
return exception.hashCode();
}
@Override
public String toString() {
return "AgentBuilder.InitializationStrategy.SelfInjection.NexusAccessor.Dispatcher.Unavailable{" +
"exception=" + exception +
'}';
}
}
}
/**
* A byte code appender for invoking a Nexus for initializing the instrumented type.
*/
protected static class InitializationAppender implements ByteCodeAppender {
/**
* The identification for the self-initialization to execute.
*/
private final int identification;
/**
* Creates a new initialization appender.
*
* @param identification The identification for the self-initialization to execute.
*/
protected InitializationAppender(int identification) {
this.identification = identification;
}
@Override
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
return new ByteCodeAppender.Simple(new StackManipulation.Compound(
MethodInvocation.invoke(NexusAccessor.INSTANCE.getSystemClassLoader),
new TextConstant(Nexus.class.getName()),
MethodInvocation.invoke(NexusAccessor.INSTANCE.loadClass),
new TextConstant("initialize"),
ArrayFactory.forType(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Class.class))
.withValues(Arrays.asList(
ClassConstant.of(TypeDescription.CLASS),
ClassConstant.of(new TypeDescription.ForLoadedType(int.class)))),
MethodInvocation.invoke(NexusAccessor.INSTANCE.getDeclaredMethod),
NullConstant.INSTANCE,
ArrayFactory.forType(TypeDescription.Generic.OBJECT)
.withValues(Arrays.asList(
ClassConstant.of(instrumentedMethod.getDeclaringType().asErasure()),
new StackManipulation.Compound(
IntegerConstant.forValue(identification),
MethodInvocation.invoke(INSTANCE.valueOf)))),
MethodInvocation.invoke(NexusAccessor.INSTANCE.invokeMethod),
Removal.SINGLE
)).apply(methodVisitor, implementationContext, instrumentedMethod);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
InitializationAppender that = (InitializationAppender) other;
return identification == that.identification;
}
@Override
public int hashCode() {
return identification;
}
@Override
public String toString() {
return "AgentBuilder.InitializationStrategy.SelfInjection.NexusAccessor.InitializationAppender{" +
"identification=" + identification +
'}';
}
}
}
}
/**
* An initialization strategy that loads auxiliary types before loading the instrumented type. This strategy skips all types
* that are a subtype of the instrumented type which would cause a premature loading of the instrumented type and abort
* the instrumentation process.
*/
enum Minimal implements InitializationStrategy, Dispatcher {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public Dispatcher dispatcher() {
return this;
}
@Override
public DynamicType.Builder> apply(DynamicType.Builder> builder) {
return builder;
}
@Override
public void register(DynamicType dynamicType, ClassLoader classLoader, InjectorFactory injectorFactory) {
Map auxiliaryTypes = dynamicType.getAuxiliaryTypes();
Map independentTypes = new LinkedHashMap(auxiliaryTypes);
for (TypeDescription auxiliaryType : auxiliaryTypes.keySet()) {
if (!auxiliaryType.getDeclaredAnnotations().isAnnotationPresent(AuxiliaryType.SignatureRelevant.class)) {
independentTypes.remove(auxiliaryType);
}
}
if (!independentTypes.isEmpty()) {
ClassInjector classInjector = injectorFactory.resolve();
Map loadedTypeInitializers = dynamicType.getLoadedTypeInitializers();
for (Map.Entry> entry : classInjector.inject(independentTypes).entrySet()) {
loadedTypeInitializers.get(entry.getKey()).onLoad(entry.getValue());
}
}
}
@Override
public String toString() {
return "AgentBuilder.InitializationStrategy.Minimal." + name();
}
}
}
/**
* A description strategy is responsible for resolving a {@link TypeDescription} when transforming or retransforming/-defining a type.
*/
interface DescriptionStrategy {
/**
* Describes the given type.
*
* @param typeName The binary name of the type to describe.
* @param typeBeingRedefined The type that is being redefined, if a redefinition is applied or {@code null} if no redefined type is available.
* @param typeLocator The type locator to use.
* @param classLoader The class loader of the type to be described.
* @param classFileLocator The class file locator of the type to be described.
* @return An appropriate type description.
*/
TypeDescription apply(String typeName, Class> typeBeingRedefined, TypeLocator typeLocator, ClassLoader classLoader, ClassFileLocator classFileLocator);
/**
* Describes the given type.
*
* @param type The loaded type to be described.
* @param typeLocator The type locator to use.
* @return An appropriate type description.
*/
TypeDescription apply(Class> type, TypeLocator typeLocator);
/**
* Default implementations of a {@link DescriptionStrategy}.
*/
enum Default implements DescriptionStrategy {
/**
* A description type strategy represents a type as a {@link com.fitbur.mockito.bytebuddy.description.type.TypeDescription.ForLoadedType} if a
* retransformation or redefinition is applied on a type. Using a loaded type typically results in better performance as no
* I/O is required for resolving type descriptions. However, any interaction with the type is carried out via the Java reflection
* API. Using the reflection API triggers eager loading of any type that is part of a method or field signature. If any of these
* types are missing from the class path, this eager loading will cause a {@link NoClassDefFoundError}. Some Java code declares
* optional dependencies to other classes which are only realized if the optional dependency is present. Such code relies on the
* Java reflection API not being used for types using optional dependencies.
*/
HYBRID {
@Override
public TypeDescription apply(String typeName,
Class> typeBeingRedefined,
TypeLocator typeLocator,
ClassLoader classLoader,
ClassFileLocator classFileLocator) {
return typeBeingRedefined == null
? POOL_ONLY.apply(typeName, UNAVAILABLE, typeLocator, classLoader, classFileLocator)
: new TypeDescription.ForLoadedType(typeBeingRedefined);
}
@Override
public TypeDescription apply(Class> type, TypeLocator typeLocator) {
return new TypeDescription.ForLoadedType(type);
}
},
/**
*
* A description strategy that always describes Java types using a {@link TypePool}. This requires that any type - even if it is already
* loaded and a {@link Class} instance is available - is processed as a non-loaded type description. Doing so can cause overhead as processing
* loaded types is supported very efficiently by a JVM.
*
*
* Avoiding the usage of loaded types can improve robustness as this approach does not rely on the Java reflection API which triggers eager
* validation of this loaded type which can fail an application if optional types are used by any types field or method signatures. Also, it
* is possible to guarantee debugging meta data to be available also for retransformed or redefined types if a {@link TypeStrategy} specifies
* the extraction of such meta data.
*
*/
POOL_ONLY {
@Override
public TypeDescription apply(String typeName,
Class> typeBeingRedefined,
TypeLocator typeLocator,
ClassLoader classLoader,
ClassFileLocator classFileLocator) {
return typeLocator.typePool(classFileLocator, classLoader).describe(typeName).resolve();
}
@Override
public TypeDescription apply(Class> type, TypeLocator typeLocator) {
return typeLocator.typePool(ClassFileLocator.ForClassLoader.of(type.getClassLoader()), type.getClassLoader()).describe(TypeDescription.ForLoadedType.getName(type)).resolve();
}
};
/**
* Indicates that no loaded type is available.
*/
private static final Class> UNAVAILABLE = null;
@Override
public String toString() {
return "AgentBuilder.DescriptionStrategy.Default." + name();
}
}
}
/**
* A redefinition strategy regulates how already loaded classes are modified by a built agent.
*/
enum RedefinitionStrategy {
/**
* Disables redefinition such that already loaded classes are not affected by the agent.
*/
DISABLED {
@Override
protected boolean isRetransforming(Instrumentation instrumentation) {
return false;
}
@Override
protected Collector makeCollector(Default.Transformation transformation) {
throw new IllegalStateException("A disabled redefinition strategy cannot create a collector");
}
},
/**
*
* Applies a redefinition to all classes that are already loaded and that would have been transformed if
* the built agent was registered before they were loaded. The created {@link ClassFileTransformer} is not
* registered for applying retransformations.
*
*
* Note: When applying a redefinition, it is normally required to use a {@link TypeStrategy} that applies
* a redefinition instead of rebasing classes such as {@link TypeStrategy.Default#REDEFINE}. Also, consider
* the constrains given by this type strategy.
*
*/
REDEFINITION {
@Override
protected boolean isRetransforming(Instrumentation instrumentation) {
if (!instrumentation.isRedefineClassesSupported()) {
throw new IllegalArgumentException("Cannot redefine classes: " + instrumentation);
}
return false;
}
@Override
protected Collector makeCollector(Default.Transformation transformation) {
return new Collector.ForRedefinition(transformation);
}
},
/**
*
* Applies a retransformation to all classes that are already loaded and that would have been transformed if
* the built agent was registered before they were loaded. The created {@link ClassFileTransformer} is registered
* for applying retransformations.
*
*
* Note: When applying a redefinition, it is normally required to use a {@link TypeStrategy} that applies
* a redefinition instead of rebasing classes such as {@link TypeStrategy.Default#REDEFINE}. Also, consider
* the constrains given by this type strategy.
*
*/
RETRANSFORMATION {
@Override
protected boolean isRetransforming(Instrumentation instrumentation) {
if (!instrumentation.isRetransformClassesSupported()) {
throw new IllegalArgumentException("Cannot retransform classes: " + instrumentation);
}
return true;
}
@Override
protected Collector makeCollector(Default.Transformation transformation) {
return new Collector.ForRetransformation(transformation);
}
};
/**
* Indicates if this strategy requires a class file transformer to be registered with a hint to apply the
* transformer for retransformation.
*
* @param instrumentation The instrumentation instance used.
* @return {@code true} if a class file transformer must be registered with a hint for retransformation.
*/
protected abstract boolean isRetransforming(Instrumentation instrumentation);
/**
* Indicates that this redefinition strategy applies a modification of already loaded classes.
*
* @return {@code true} if this redefinition strategy applies a modification of already loaded classes.
*/
protected boolean isEnabled() {
return this != DISABLED;
}
/**
* Creates a collector instance that is responsible for collecting loaded classes for potential retransformation.
*
* @param transformation The transformation that is registered for the agent.
* @return A new collector for collecting already loaded classes for transformation.
*/
protected abstract Collector makeCollector(Default.Transformation transformation);
@Override
public String toString() {
return "AgentBuilder.RedefinitionStrategy." + name();
}
/**
* A collector is responsible for collecting classes that are to be considered for modification.
*/
protected interface Collector {
/**
* Considers a loaded class for modification.
*
* @param typeDescription The type description of the type that is to be considered.
* @param type The loaded representation of the type that is to be considered.
* @param ignoredTypeMatcher Identifies types that should not be instrumented.
* @return {@code true} if the class is considered to be redefined.
*/
boolean consider(TypeDescription typeDescription, Class> type, RawMatcher ignoredTypeMatcher);
/**
* Applies this collector.
*
* @param instrumentation The instrumentation instance to apply the transformation for.
* @param typeLocator The type locator to use.
* @param listener the listener to notify.
* @throws UnmodifiableClassException If a class is not modifiable.
* @throws ClassNotFoundException If a class could not be found.
*/
void apply(Instrumentation instrumentation,
TypeLocator typeLocator,
Listener listener) throws UnmodifiableClassException, ClassNotFoundException;
/**
* A collector that applies a redefinition of already loaded classes.
*/
class ForRedefinition implements Collector {
/**
* The transformation of the built agent.
*/
private final Default.Transformation transformation;
/**
* A list of already collected redefinitions.
*/
private final List entries;
/**
* Creates a new collector for a redefinition.
*
* @param transformation The transformation of the built agent.
*/
protected ForRedefinition(Default.Transformation transformation) {
this.transformation = transformation;
entries = new ArrayList();
}
@Override
public boolean consider(TypeDescription typeDescription, Class> type, RawMatcher ignoredTypeMatcher) {
return transformation.resolve(typeDescription,
type.getClassLoader(),
JavaModule.ofType(type),
type,
type.getProtectionDomain(),
ignoredTypeMatcher).getSort().isAlive() && entries.add(new Entry(type));
}
@Override
public void apply(Instrumentation instrumentation, TypeLocator typeLocator, Listener listener) throws UnmodifiableClassException, ClassNotFoundException {
List classDefinitions = new ArrayList(entries.size());
for (Entry entry : entries) {
JavaModule module = JavaModule.ofType(entry.getType());
try {
classDefinitions.add(entry.resolve(ClassFileLocator.ForClassLoader.of(entry.getType().getClassLoader())));
} catch (Throwable throwable) {
try {
listener.onError(TypeDescription.ForLoadedType.getName(entry.getType()), entry.getType().getClassLoader(), module, throwable);
} finally {
listener.onComplete(TypeDescription.ForLoadedType.getName(entry.getType()), entry.getType().getClassLoader(), module);
}
}
}
if (!classDefinitions.isEmpty()) {
instrumentation.redefineClasses(classDefinitions.toArray(new ClassDefinition[classDefinitions.size()]));
}
}
@Override
public String toString() {
return "AgentBuilder.RedefinitionStrategy.Collector.ForRedefinition{" +
"transformation=" + transformation +
", entries=" + entries +
'}';
}
/**
* An entry describing a type redefinition.
*/
protected static class Entry {
/**
* The type to be redefined.
*/
private final Class> type;
/**
* Creates a new entry for a given type.
*
* @param type The type to be redefined.
*/
protected Entry(Class> type) {
this.type = type;
}
/**
* Returns the type that is being redefined.
*
* @return The type that is being redefined.
*/
public Class> getType() {
return type;
}
/**
* Resolves the entry to a class definition.
*
* @param classFileLocator The class file locator for locating the redefine type's class file.
* @return A class definition representing the redefined class.
* @throws IOException If an IO exception occurs.
*/
protected ClassDefinition resolve(ClassFileLocator classFileLocator) throws IOException {
return new ClassDefinition(type, classFileLocator.locate(TypeDescription.ForLoadedType.getName(type)).resolve());
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Entry entry = (Entry) other;
return type.equals(entry.type);
}
@Override
public int hashCode() {
return type.hashCode();
}
@Override
public String toString() {
return "AgentBuilder.RedefinitionStrategy.Collector.ForRedefinition.Entry{" +
"type=" + type +
'}';
}
}
}
/**
* A collector that applies a retransformation of already loaded classes.
*/
class ForRetransformation implements Collector {
/**
* The transformation defined by the built agent.
*/
private final Default.Transformation transformation;
/**
* The types that were collected for retransformation.
*/
private final List> types;
/**
* Creates a new collector for a retransformation.
*
* @param transformation The transformation defined by the built agent.
*/
protected ForRetransformation(Default.Transformation transformation) {
this.transformation = transformation;
types = new ArrayList>();
}
@Override
public boolean consider(TypeDescription typeDescription, Class> type, RawMatcher ignoredTypeMatcher) {
return transformation.resolve(typeDescription,
type.getClassLoader(),
JavaModule.ofType(type),
type,
type.getProtectionDomain(),
ignoredTypeMatcher).getSort().isAlive() && types.add(type);
}
@Override
public void apply(Instrumentation instrumentation, TypeLocator typeLocator, Listener listener) throws UnmodifiableClassException {
if (!types.isEmpty()) {
instrumentation.retransformClasses(types.toArray(new Class>[types.size()]));
}
}
@Override
public String toString() {
return "AgentBuilder.RedefinitionStrategy.Collector.ForRetransformation{" +
"transformation=" + transformation +
", types=" + types +
'}';
}
}
}
}
/**
* Implements the instrumentation of the {@code LambdaMetafactory} if this feature is enabled.
*/
enum LambdaInstrumentationStrategy implements Callable> {
/**
* A strategy that enables instrumentation of the {@code LambdaMetafactory} if such a factory exists on the current VM.
* Classes representing lambda expressions that are created by Byte Buddy are fully compatible to those created by
* the JVM and can be serialized or deserialized to one another. The classes do however show a few differences:
*
* - Byte Buddy's classes are public with a public executing transformer. Doing so, it is not necessary to instantiate a
* non-capturing lambda expression by reflection. This is done because Byte Buddy is not necessarily capable
* of using reflection due to an active security manager.
* - Byte Buddy's classes are not marked as synthetic as an agent builder does not instrument synthetic classes
* by default.
*
*/
ENABLED {
@Override
protected void apply(ByteBuddy byteBuddy, Instrumentation instrumentation, ClassFileTransformer classFileTransformer) {
if (LambdaFactory.register(classFileTransformer, new LambdaInstanceFactory(byteBuddy), this)) {
Class> lambdaMetaFactory;
try {
lambdaMetaFactory = Class.forName("java.lang.invoke.LambdaMetafactory");
} catch (ClassNotFoundException ignored) {
return;
}
byteBuddy.with(Implementation.Context.Disabled.Factory.INSTANCE)
.redefine(lambdaMetaFactory)
.visit(new AsmVisitorWrapper.ForDeclaredMethods()
.method(named("metafactory"), MetaFactoryRedirection.INSTANCE)
.method(named("altMetafactory"), AlternativeMetaFactoryRedirection.INSTANCE))
.make()
.load(lambdaMetaFactory.getClassLoader(), ClassReloadingStrategy.of(instrumentation));
}
}
@Override
public Class> call() throws Exception {
TypeDescription lambdaFactory = new TypeDescription.ForLoadedType(LambdaFactory.class);
return ClassInjector.UsingReflection.ofSystemClassLoader()
.inject(Collections.singletonMap(lambdaFactory, ClassFileLocator.ForClassLoader.read(LambdaFactory.class).resolve()))
.get(lambdaFactory);
}
},
/**
* A strategy that does not instrument the {@code LambdaMetafactory}.
*/
DISABLED {
@Override
protected void apply(ByteBuddy byteBuddy, Instrumentation instrumentation, ClassFileTransformer classFileTransformer) {
/* do nothing */
}
@Override
public Class> call() throws Exception {
throw new IllegalStateException("Cannot inject LambdaFactory from disabled instrumentation strategy");
}
};
/**
* Indicates that an original implementation can be ignored when redefining a method.
*/
protected static final MethodVisitor IGNORE_ORIGINAL = null;
/**
* Releases the supplied class file transformer when it was built with {@link AgentBuilder#with(LambdaInstrumentationStrategy)} enabled.
* Subsequently, the class file transformer is no longer applied when a class that represents a lambda expression is created.
*
* @param classFileTransformer The class file transformer to release.
* @param instrumentation The instrumentation instance that is used to potentially rollback the instrumentation of the {@code LambdaMetafactory}.
*/
public static void release(ClassFileTransformer classFileTransformer, Instrumentation instrumentation) {
if (LambdaFactory.release(classFileTransformer)) {
try {
ClassReloadingStrategy.of(instrumentation).reset(Class.forName("java.lang.invoke.LambdaMetafactory"));
} catch (Exception exception) {
throw new IllegalStateException("Could not release lambda transformer", exception);
}
}
}
/**
* Returns an enabled lambda instrumentation strategy for {@code true}.
*
* @param enabled If lambda instrumentation should be enabled.
* @return {@code true} if the returned strategy should be enabled.
*/
public static LambdaInstrumentationStrategy of(boolean enabled) {
return enabled
? ENABLED
: DISABLED;
}
/**
* Applies a transformation to lambda instances if applicable.
*
* @param byteBuddy The Byte Buddy instance to use.
* @param instrumentation The instrumentation instance for applying a redefinition.
* @param classFileTransformer The class file transformer to apply.
*/
protected abstract void apply(ByteBuddy byteBuddy, Instrumentation instrumentation, ClassFileTransformer classFileTransformer);
/**
* Indicates if this strategy enables instrumentation of the {@code LambdaMetafactory}.
*
* @return {@code true} if this strategy is enabled.
*/
public boolean isEnabled() {
return this == ENABLED;
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy." + name();
}
/**
* A factory that creates instances that represent lambda expressions.
*/
protected static class LambdaInstanceFactory {
/**
* The name of a factory for a lambda expression.
*/
private static final String LAMBDA_FACTORY = "get$Lambda";
/**
* A prefix for a field that represents a property of a lambda expression.
*/
private static final String FIELD_PREFIX = "arg$";
/**
* The infix to use for naming classes that represent lambda expression. The additional prefix
* is necessary because the subsequent counter is not sufficient to keep names unique compared
* to the original factory.
*/
private static final String LAMBDA_TYPE_INFIX = "$$Lambda$ByteBuddy$";
/**
* A type-safe constant to express that a class is not already loaded when applying a class file transformer.
*/
private static final Class> NOT_PREVIOUSLY_DEFINED = null;
/**
* A counter for naming lambda expressions randomly.
*/
private static final AtomicInteger LAMBDA_NAME_COUNTER = new AtomicInteger();
/**
* The Byte Buddy instance to use for creating lambda objects.
*/
private final ByteBuddy byteBuddy;
/**
* Creates a new lambda instance factory.
*
* @param byteBuddy The Byte Buddy instance to use for creating lambda objects.
*/
protected LambdaInstanceFactory(ByteBuddy byteBuddy) {
this.byteBuddy = byteBuddy;
}
/**
* Applies this lambda meta factory.
*
* @param targetTypeLookup A lookup context representing the creating class of this lambda expression.
* @param lambdaMethodName The name of the lambda expression's represented method.
* @param factoryMethodType The type of the lambda expression's represented method.
* @param lambdaMethodType The type of the lambda expression's factory method.
* @param targetMethodHandle A handle representing the target of the lambda expression's method.
* @param specializedLambdaMethodType A specialization of the type of the lambda expression's represented method.
* @param serializable {@code true} if the lambda expression should be serializable.
* @param markerInterfaces A list of interfaces for the lambda expression to represent.
* @param additionalBridges A list of additional bridge methods to be implemented by the lambda expression.
* @param classFileTransformers A collection of class file transformers to apply when creating the class.
* @return A binary representation of the transformed class file.
*/
public byte[] make(Object targetTypeLookup,
String lambdaMethodName,
Object factoryMethodType,
Object lambdaMethodType,
Object targetMethodHandle,
Object specializedLambdaMethodType,
boolean serializable,
List> markerInterfaces,
List> additionalBridges,
Collection extends ClassFileTransformer> classFileTransformers) {
JavaConstant.MethodType factoryMethod = JavaConstant.MethodType.ofLoaded(factoryMethodType);
JavaConstant.MethodType lambdaMethod = JavaConstant.MethodType.ofLoaded(lambdaMethodType);
JavaConstant.MethodHandle targetMethod = JavaConstant.MethodHandle.ofLoaded(targetMethodHandle, targetTypeLookup);
JavaConstant.MethodType specializedLambdaMethod = JavaConstant.MethodType.ofLoaded(specializedLambdaMethodType);
Class> targetType = JavaConstant.MethodHandle.lookupType(targetTypeLookup);
String lambdaClassName = targetType.getName() + LAMBDA_TYPE_INFIX + LAMBDA_NAME_COUNTER.incrementAndGet();
DynamicType.Builder> builder = byteBuddy
.subclass(factoryMethod.getReturnType(), ConstructorStrategy.Default.NO_CONSTRUCTORS)
.modifiers(TypeManifestation.FINAL, Visibility.PUBLIC)
.implement(markerInterfaces)
.name(lambdaClassName)
.defineConstructor(Visibility.PUBLIC)
.withParameters(factoryMethod.getParameterTypes())
.intercept(ConstructorImplementation.INSTANCE)
.method(named(lambdaMethodName)
.and(takesArguments(lambdaMethod.getParameterTypes()))
.and(returns(lambdaMethod.getReturnType())))
.intercept(new LambdaMethodImplementation(targetMethod, specializedLambdaMethod));
int index = 0;
for (TypeDescription capturedType : factoryMethod.getParameterTypes()) {
builder = builder.defineField(FIELD_PREFIX + ++index, capturedType, Visibility.PRIVATE, FieldManifestation.FINAL);
}
if (!factoryMethod.getParameterTypes().isEmpty()) {
builder = builder.defineMethod(LAMBDA_FACTORY, factoryMethod.getReturnType(), Visibility.PRIVATE, Ownership.STATIC)
.withParameters(factoryMethod.getParameterTypes())
.intercept(FactoryImplementation.INSTANCE);
}
if (serializable) {
if (!markerInterfaces.contains(Serializable.class)) {
builder = builder.implement(Serializable.class);
}
builder = builder.defineMethod("writeReplace", Object.class, Visibility.PRIVATE)
.intercept(new SerializationImplementation(new TypeDescription.ForLoadedType(targetType),
factoryMethod.getReturnType(),
lambdaMethodName,
lambdaMethod,
targetMethod,
JavaConstant.MethodType.ofLoaded(specializedLambdaMethodType)));
} else if (factoryMethod.getReturnType().isAssignableTo(Serializable.class)) {
builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE)
.withParameters(ObjectInputStream.class)
.throwing(NotSerializableException.class)
.intercept(ExceptionMethod.throwing(NotSerializableException.class, "Non-serializable lambda"))
.defineMethod("writeObject", void.class, Visibility.PRIVATE)
.withParameters(ObjectOutputStream.class)
.throwing(NotSerializableException.class)
.intercept(ExceptionMethod.throwing(NotSerializableException.class, "Non-serializable lambda"));
}
for (Object additionalBridgeType : additionalBridges) {
JavaConstant.MethodType additionalBridge = JavaConstant.MethodType.ofLoaded(additionalBridgeType);
builder = builder.defineMethod(lambdaMethodName, additionalBridge.getReturnType(), MethodManifestation.BRIDGE, Visibility.PUBLIC)
.withParameters(additionalBridge.getParameterTypes())
.intercept(new BridgeMethodImplementation(lambdaMethodName, lambdaMethod));
}
byte[] classFile = builder.make().getBytes();
for (ClassFileTransformer classFileTransformer : classFileTransformers) {
try {
byte[] transformedClassFile = classFileTransformer.transform(targetType.getClassLoader(),
lambdaClassName.replace('.', '/'),
NOT_PREVIOUSLY_DEFINED,
targetType.getProtectionDomain(),
classFile);
classFile = transformedClassFile == null
? classFile
: transformedClassFile;
} catch (Throwable ignored) {
/* do nothing */
}
}
return classFile;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& byteBuddy.equals(((LambdaInstanceFactory) other).byteBuddy);
}
@Override
public int hashCode() {
return byteBuddy.hashCode();
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy.LambdaInstanceFactory{" +
"byteBuddy=" + byteBuddy +
'}';
}
/**
* Implements a lambda class's executing transformer.
*/
@SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "An enumeration does not serialize fields")
protected enum ConstructorImplementation implements Implementation {
/**
* The singleton instance.
*/
INSTANCE;
/**
* A reference to the {@link Object} class's default executing transformer.
*/
private final MethodDescription.InDefinedShape objectConstructor;
/**
* Creates a new executing transformer implementation.
*/
ConstructorImplementation() {
objectConstructor = TypeDescription.OBJECT.getDeclaredMethods().filter(isConstructor()).getOnly();
}
@Override
public ByteCodeAppender appender(Target implementationTarget) {
return new Appender(implementationTarget.getInstrumentedType().getDeclaredFields());
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy.LambdaInstanceFactory.ConstructorImplementation." + name();
}
/**
* An appender to implement the executing transformer.
*/
protected static class Appender implements ByteCodeAppender {
/**
* The fields that are declared by the instrumented type.
*/
private final List declaredFields;
/**
* Creates a new appender.
*
* @param declaredFields The fields that are declared by the instrumented type.
*/
protected Appender(List declaredFields) {
this.declaredFields = declaredFields;
}
@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
List fieldAssignments = new ArrayList(declaredFields.size() * 3);
for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) {
fieldAssignments.add(MethodVariableAccess.REFERENCE.loadOffset(0));
fieldAssignments.add(MethodVariableAccess.of(parameterDescription.getType()).loadOffset(parameterDescription.getOffset()));
fieldAssignments.add(FieldAccess.forField(declaredFields.get(parameterDescription.getIndex())).putter());
}
return new Size(new StackManipulation.Compound(
MethodVariableAccess.REFERENCE.loadOffset(0),
MethodInvocation.invoke(INSTANCE.objectConstructor),
new StackManipulation.Compound(fieldAssignments),
MethodReturn.VOID
).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& declaredFields.equals(((Appender) other).declaredFields);
}
@Override
public int hashCode() {
return declaredFields.hashCode();
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy.LambdaInstanceFactory.ConstructorImplementation.Appender{" +
"declaredFields=" + declaredFields +
'}';
}
}
}
/**
* An implementation of a instance factory for a lambda expression's class.
*/
protected enum FactoryImplementation implements Implementation {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public ByteCodeAppender appender(Target implementationTarget) {
return new Appender(implementationTarget.getInstrumentedType());
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy.LambdaInstanceFactory.FactoryImplementation." + name();
}
/**
* An appender for a lambda expression factory.
*/
protected static class Appender implements ByteCodeAppender {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* Creates a new appender.
*
* @param instrumentedType The instrumented type.
*/
protected Appender(TypeDescription instrumentedType) {
this.instrumentedType = instrumentedType;
}
@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
return new Size(new StackManipulation.Compound(
TypeCreation.of(instrumentedType),
Duplication.SINGLE,
MethodVariableAccess.allArgumentsOf(instrumentedMethod),
MethodInvocation.invoke(instrumentedType.getDeclaredMethods().filter(isConstructor()).getOnly()),
MethodReturn.REFERENCE
).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& instrumentedType.equals(((Appender) other).instrumentedType);
}
@Override
public int hashCode() {
return instrumentedType.hashCode();
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy.LambdaInstanceFactory.FactoryImplementation.Appender{" +
"instrumentedType=" + instrumentedType +
'}';
}
}
}
/**
* Implements a lambda expression's functional method.
*/
protected static class LambdaMethodImplementation implements Implementation {
/**
* The handle of the target method of the lambda expression.
*/
private final JavaConstant.MethodHandle targetMethod;
/**
* The specialized type of the lambda method.
*/
private final JavaConstant.MethodType specializedLambdaMethod;
/**
* Creates a implementation of a lambda expression's functional method.
*
* @param targetMethod The target method of the lambda expression.
* @param specializedLambdaMethod The specialized type of the lambda method.
*/
protected LambdaMethodImplementation(JavaConstant.MethodHandle targetMethod, JavaConstant.MethodType specializedLambdaMethod) {
this.targetMethod = targetMethod;
this.specializedLambdaMethod = specializedLambdaMethod;
}
@Override
public ByteCodeAppender appender(Target implementationTarget) {
return new Appender(targetMethod.getOwnerType()
.getDeclaredMethods()
.filter(named(targetMethod.getName())
.and(returns(targetMethod.getReturnType()))
.and(takesArguments(targetMethod.getParameterTypes())))
.getOnly(),
specializedLambdaMethod,
implementationTarget.getInstrumentedType().getDeclaredFields());
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
LambdaMethodImplementation that = (LambdaMethodImplementation) other;
return targetMethod.equals(that.targetMethod)
&& specializedLambdaMethod.equals(that.specializedLambdaMethod);
}
@Override
public int hashCode() {
int result = targetMethod.hashCode();
result = 31 * result + specializedLambdaMethod.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy.LambdaInstanceFactory.LambdaMethodImplementation{" +
"targetMethod=" + targetMethod +
", specializedLambdaMethod=" + specializedLambdaMethod +
'}';
}
/**
* An appender for a lambda expression's functional method.
*/
protected static class Appender implements ByteCodeAppender {
/**
* The target method of the lambda expression.
*/
private final MethodDescription targetMethod;
/**
* The specialized type of the lambda method.
*/
private final JavaConstant.MethodType specializedLambdaMethod;
/**
* The instrumented type's declared fields.
*/
private final List declaredFields;
/**
* Creates an appender of a lambda expression's functional method.
*
* @param targetMethod The target method of the lambda expression.
* @param specializedLambdaMethod The specialized type of the lambda method.
* @param declaredFields The instrumented type's declared fields.
*/
protected Appender(MethodDescription targetMethod,
JavaConstant.MethodType specializedLambdaMethod,
List declaredFields) {
this.targetMethod = targetMethod;
this.specializedLambdaMethod = specializedLambdaMethod;
this.declaredFields = declaredFields;
}
@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
List fieldAccess = new ArrayList(declaredFields.size() * 2);
for (FieldDescription.InDefinedShape fieldDescription : declaredFields) {
fieldAccess.add(MethodVariableAccess.REFERENCE.loadOffset(0));
fieldAccess.add(FieldAccess.forField(fieldDescription).getter());
}
List parameterAccess = new ArrayList(instrumentedMethod.getParameters().size() * 2);
for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) {
parameterAccess.add(MethodVariableAccess.of(parameterDescription.getType()).loadOffset(parameterDescription.getOffset()));
parameterAccess.add(Assigner.DEFAULT.assign(parameterDescription.getType(),
specializedLambdaMethod.getParameterTypes().get(parameterDescription.getIndex()).asGenericType(),
Assigner.Typing.DYNAMIC));
}
return new Size(new StackManipulation.Compound(
new StackManipulation.Compound(fieldAccess),
new StackManipulation.Compound(parameterAccess),
MethodInvocation.invoke(targetMethod),
MethodReturn.returning(targetMethod.getReturnType().asErasure())
).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Appender appender = (Appender) other;
return targetMethod.equals(appender.targetMethod)
&& declaredFields.equals(appender.declaredFields)
&& specializedLambdaMethod.equals(appender.specializedLambdaMethod);
}
@Override
public int hashCode() {
int result = targetMethod.hashCode();
result = 31 * result + declaredFields.hashCode();
result = 31 * result + specializedLambdaMethod.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy.LambdaInstanceFactory.LambdaMethodImplementation.Appender{" +
"targetMethod=" + targetMethod +
", specializedLambdaMethod=" + specializedLambdaMethod +
", declaredFields=" + declaredFields +
'}';
}
}
}
/**
* Implements the {@code writeReplace} method for serializable lambda expressions.
*/
protected static class SerializationImplementation implements Implementation {
/**
* The lambda expression's declaring type.
*/
private final TypeDescription targetType;
/**
* The lambda expression's functional type.
*/
private final TypeDescription lambdaType;
/**
* The lambda expression's functional method name.
*/
private final String lambdaMethodName;
/**
* The method type of the lambda expression's functional method.
*/
private final JavaConstant.MethodType lambdaMethod;
/**
* A handle that references the lambda expressions invocation target.
*/
private final JavaConstant.MethodHandle targetMethod;
/**
* The specialized method type of the lambda expression's functional method.
*/
private final JavaConstant.MethodType specializedMethod;
/**
* Creates a new implementation for a serializable's lambda expression's {@code writeReplace} method.
*
* @param targetType The lambda expression's declaring type.
* @param lambdaType The lambda expression's functional type.
* @param lambdaMethodName The lambda expression's functional method name.
* @param lambdaMethod The method type of the lambda expression's functional method.
* @param targetMethod A handle that references the lambda expressions invocation target.
* @param specializedMethod The specialized method type of the lambda expression's functional method.
*/
protected SerializationImplementation(TypeDescription targetType,
TypeDescription lambdaType,
String lambdaMethodName,
JavaConstant.MethodType lambdaMethod,
JavaConstant.MethodHandle targetMethod,
JavaConstant.MethodType specializedMethod) {
this.targetType = targetType;
this.lambdaType = lambdaType;
this.lambdaMethodName = lambdaMethodName;
this.lambdaMethod = lambdaMethod;
this.targetMethod = targetMethod;
this.specializedMethod = specializedMethod;
}
@Override
public ByteCodeAppender appender(Target implementationTarget) {
TypeDescription serializedLambda;
try {
serializedLambda = new TypeDescription.ForLoadedType(Class.forName("java.lang.invoke.SerializedLambda"));
} catch (ClassNotFoundException exception) {
throw new IllegalStateException("Cannot find class for lambda serialization", exception);
}
List lambdaArguments = new ArrayList(implementationTarget.getInstrumentedType().getDeclaredFields().size());
for (FieldDescription.InDefinedShape fieldDescription : implementationTarget.getInstrumentedType().getDeclaredFields()) {
lambdaArguments.add(new StackManipulation.Compound(MethodVariableAccess.REFERENCE.loadOffset(0),
FieldAccess.forField(fieldDescription).getter(),
Assigner.DEFAULT.assign(fieldDescription.getType(), TypeDescription.Generic.OBJECT, Assigner.Typing.STATIC)));
}
return new ByteCodeAppender.Simple(new StackManipulation.Compound(
TypeCreation.of(serializedLambda),
Duplication.SINGLE,
ClassConstant.of(targetType),
new TextConstant(lambdaType.getInternalName()),
new TextConstant(lambdaMethodName),
new TextConstant(lambdaMethod.getDescriptor()),
IntegerConstant.forValue(targetMethod.getHandleType().getIdentifier()),
new TextConstant(targetMethod.getOwnerType().getInternalName()),
new TextConstant(targetMethod.getName()),
new TextConstant(targetMethod.getDescriptor()),
new TextConstant(specializedMethod.getDescriptor()),
ArrayFactory.forType(TypeDescription.Generic.OBJECT).withValues(lambdaArguments),
MethodInvocation.invoke(serializedLambda.getDeclaredMethods().filter(isConstructor()).getOnly()),
MethodReturn.REFERENCE
));
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
SerializationImplementation that = (SerializationImplementation) other;
return targetType.equals(that.targetType)
&& lambdaType.equals(that.lambdaType)
&& lambdaMethodName.equals(that.lambdaMethodName)
&& lambdaMethod.equals(that.lambdaMethod)
&& targetMethod.equals(that.targetMethod)
&& specializedMethod.equals(that.specializedMethod);
}
@Override
public int hashCode() {
int result = targetType.hashCode();
result = 31 * result + lambdaType.hashCode();
result = 31 * result + lambdaMethodName.hashCode();
result = 31 * result + lambdaMethod.hashCode();
result = 31 * result + targetMethod.hashCode();
result = 31 * result + specializedMethod.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy.LambdaInstanceFactory.SerializationImplementation{" +
"targetType=" + targetType +
", lambdaType=" + lambdaType +
", lambdaMethodName='" + lambdaMethodName + '\'' +
", lambdaMethod=" + lambdaMethod +
", targetMethod=" + targetMethod +
", specializedMethod=" + specializedMethod +
'}';
}
}
/**
* Implements an explicit bridge method for a lambda expression.
*/
protected static class BridgeMethodImplementation implements Implementation {
/**
* The name of the lambda expression's functional method.
*/
private final String lambdaMethodName;
/**
* The actual type of the lambda expression's functional method.
*/
private final JavaConstant.MethodType lambdaMethod;
/**
* Creates a new bridge method implementation for a lambda expression.
*
* @param lambdaMethodName The name of the lambda expression's functional method.
* @param lambdaMethod The actual type of the lambda expression's functional method.
*/
protected BridgeMethodImplementation(String lambdaMethodName, JavaConstant.MethodType lambdaMethod) {
this.lambdaMethodName = lambdaMethodName;
this.lambdaMethod = lambdaMethod;
}
@Override
public ByteCodeAppender appender(Target implementationTarget) {
return new Appender(implementationTarget.invokeSuper(new MethodDescription.SignatureToken(lambdaMethodName,
lambdaMethod.getReturnType(),
lambdaMethod.getParameterTypes())));
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
BridgeMethodImplementation that = (BridgeMethodImplementation) other;
return lambdaMethodName.equals(that.lambdaMethodName) && lambdaMethod.equals(that.lambdaMethod);
}
@Override
public int hashCode() {
int result = lambdaMethodName.hashCode();
result = 31 * result + lambdaMethod.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy.LambdaInstanceFactory.BridgeMethodImplementation{" +
"lambdaMethodName='" + lambdaMethodName + '\'' +
", lambdaMethod=" + lambdaMethod +
'}';
}
/**
* An appender for implementing a bridge method for a lambda expression.
*/
protected static class Appender implements ByteCodeAppender {
/**
* The invocation of the bridge's target method.
*/
private final SpecialMethodInvocation bridgeTargetInvocation;
/**
* Creates a new appender for invoking a lambda expression's bridge method target.
*
* @param bridgeTargetInvocation The invocation of the bridge's target method.
*/
protected Appender(SpecialMethodInvocation bridgeTargetInvocation) {
this.bridgeTargetInvocation = bridgeTargetInvocation;
}
@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
return new Compound(new Simple(
MethodVariableAccess.allArgumentsOf(instrumentedMethod)
.asBridgeOf(bridgeTargetInvocation.getMethodDescription())
.prependThisReference(),
bridgeTargetInvocation,
bridgeTargetInvocation.getMethodDescription().getReturnType().asErasure().isAssignableTo(instrumentedMethod.getReturnType().asErasure())
? StackManipulation.Trivial.INSTANCE
: TypeCasting.to(instrumentedMethod.getReceiverType().asErasure()),
MethodReturn.returning(instrumentedMethod.getReturnType().asErasure())
)).apply(methodVisitor, implementationContext, instrumentedMethod);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& bridgeTargetInvocation.equals(((Appender) other).bridgeTargetInvocation);
}
@Override
public int hashCode() {
return bridgeTargetInvocation.hashCode();
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy.LambdaInstanceFactory.BridgeMethodImplementation.Appender{" +
"bridgeTargetInvocation=" + bridgeTargetInvocation +
'}';
}
}
}
}
/**
* Implements the regular lambda meta factory. The implementation represents the following code:
*
* public static CallSite metafactory(MethodHandles.Lookup caller,
* String invokedName,
* MethodType invokedType,
* MethodType samMethodType,
* MethodHandle implMethod,
* MethodType instantiatedMethodType) throws Exception {
* Unsafe unsafe = Unsafe.getUnsafe();
* {@code Class>} lambdaClass = unsafe.defineAnonymousClass(caller.lookupClass(),
* (byte[]) ClassLoader.getSystemClassLoader().loadClass("com.fitbur.mockito.bytebuddy.agent.builder.LambdaFactory").getDeclaredMethod("make",
* Object.class,
* String.class,
* Object.class,
* Object.class,
* Object.class,
* Object.class,
* boolean.class,
* List.class,
* List.class).invoke(null,
* caller,
* invokedName,
* invokedType,
* samMethodType,
* implMethod,
* instantiatedMethodType,
* false,
* Collections.emptyList(),
* Collections.emptyList()),
* null);
* unsafe.ensureClassInitialized(lambdaClass);
* return invokedType.parameterCount() == 0
* ? new ConstantCallSite(MethodHandles.constant(invokedType.returnType(), lambdaClass.getDeclaredConstructors()[0].newInstance()))
* : new ConstantCallSite(MethodHandles.Lookup.IMPL_LOOKUP.findStatic(lambdaClass, "get$Lambda", invokedType));
*
*/
protected enum MetaFactoryRedirection implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public MethodVisitor wrap(TypeDescription instrumentedType,
MethodDescription.InDefinedShape methodDescription,
MethodVisitor methodVisitor,
ClassFileVersion classFileVersion,
int writerFlags,
int readerFlags) {
methodVisitor.visitCode();
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "sun/misc/Unsafe", "getUnsafe", "()Lsun/misc/Unsafe;", false);
methodVisitor.visitVarInsn(Opcodes.ASTORE, 6);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 6);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "lookupClass", "()Ljava/lang/Class;", false);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false);
methodVisitor.visitLdcInsn("com.fitbur.mockito.bytebuddy.agent.builder.LambdaFactory");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false);
methodVisitor.visitLdcInsn("make");
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9);
methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_1);
methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/String;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_2);
methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_3);
methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_4);
methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_5);
methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6);
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7);
methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8);
methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
methodVisitor.visitInsn(Opcodes.ACONST_NULL);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9);
methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_1);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_2);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_3);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_4);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 4);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_5);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 5);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Collections", "emptyList", "()Ljava/util/List;", false);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Collections", "emptyList", "()Ljava/util/List;", false);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false);
methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "[B");
methodVisitor.visitInsn(Opcodes.ACONST_NULL);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "sun/misc/Unsafe", "defineAnonymousClass", "(Ljava/lang/Class;[B[Ljava/lang/Object;)Ljava/lang/Class;", false);
methodVisitor.visitVarInsn(Opcodes.ASTORE, 7);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 6);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 7);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "sun/misc/Unsafe", "ensureClassInitialized", "(Ljava/lang/Class;)V", false);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "parameterCount", "()I", false);
Label conditionalDefault = new Label();
methodVisitor.visitJumpInsn(Opcodes.IFNE, conditionalDefault);
methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite");
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "returnType", "()Ljava/lang/Class;", false);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 7);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", false);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitInsn(Opcodes.AALOAD);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Constructor", "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;", false);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandles", "constant", "(Ljava/lang/Class;Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;", false);
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", "(Ljava/lang/invoke/MethodHandle;)V", false);
Label conditionalAlternative = new Label();
methodVisitor.visitJumpInsn(Opcodes.GOTO, conditionalAlternative);
methodVisitor.visitLabel(conditionalDefault);
methodVisitor.visitFrame(Opcodes.F_APPEND, 2, new Object[]{"sun/misc/Unsafe", "java/lang/Class"}, 0, null);
methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite");
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/invoke/MethodHandles$Lookup", "IMPL_LOOKUP", "Ljava/lang/invoke/MethodHandles$Lookup;");
methodVisitor.visitVarInsn(Opcodes.ALOAD, 7);
methodVisitor.visitLdcInsn("get$Lambda");
methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false);
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", "(Ljava/lang/invoke/MethodHandle;)V", false);
methodVisitor.visitLabel(conditionalAlternative);
methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/invoke/CallSite"});
methodVisitor.visitInsn(Opcodes.ARETURN);
methodVisitor.visitMaxs(8, 8);
methodVisitor.visitEnd();
return IGNORE_ORIGINAL;
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy.MetaFactoryRedirection." + name();
}
}
/**
* Implements the alternative lambda meta factory. The implementation represents the following code:
*
* public static CallSite altMetafactory(MethodHandles.Lookup caller,
* String invokedName,
* MethodType invokedType,
* Object... args) throws Exception {
* int flags = (Integer) args[3];
* int argIndex = 4;
* {@code Class>[]} markerInterface;
* if ((flags {@code &} FLAG_MARKERS) != 0) {
* int markerCount = (Integer) args[argIndex++];
* markerInterface = new {@code Class>}[markerCount];
* System.arraycopy(args, argIndex, markerInterface, 0, markerCount);
* argIndex += markerCount;
* } else {
* markerInterface = new {@code Class>}[0];
* }
* MethodType[] additionalBridge;
* if ((flags {@code &} FLAG_BRIDGES) != 0) {
* int bridgeCount = (Integer) args[argIndex++];
* additionalBridge = new MethodType[bridgeCount];
* System.arraycopy(args, argIndex, additionalBridge, 0, bridgeCount);
* // argIndex += bridgeCount;
* } else {
* additionalBridge = new MethodType[0];
* }
* Unsafe unsafe = Unsafe.getUnsafe();
* {@code Class>} lambdaClass = unsafe.defineAnonymousClass(caller.lookupClass(),
* (byte[]) ClassLoader.getSystemClassLoader().loadClass("com.fitbur.mockito.bytebuddy.agent.builder.LambdaFactory").getDeclaredMethod("make",
* Object.class,
* String.class,
* Object.class,
* Object.class,
* Object.class,
* Object.class,
* boolean.class,
* List.class,
* List.class).invoke(null,
* caller,
* invokedName,
* invokedType,
* args[0],
* args[1],
* args[2],
* (flags {@code &} FLAG_SERIALIZABLE) != 0,
* Arrays.asList(markerInterface),
* Arrays.asList(additionalBridge)),
* null);
* unsafe.ensureClassInitialized(lambdaClass);
* return invokedType.parameterCount() == 0
* ? new ConstantCallSite(MethodHandles.constant(invokedType.returnType(), lambdaClass.getDeclaredConstructors()[0].newInstance()))
* : new ConstantCallSite(MethodHandles.Lookup.IMPL_LOOKUP.findStatic(lambdaClass, "get$Lambda", invokedType));
* }
*
*/
protected enum AlternativeMetaFactoryRedirection implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public MethodVisitor wrap(TypeDescription instrumentedType,
MethodDescription.InDefinedShape methodDescription,
MethodVisitor methodVisitor,
ClassFileVersion classFileVersion,
int writerFlags,
int readerFlags) {
methodVisitor.visitCode();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
methodVisitor.visitInsn(Opcodes.ICONST_3);
methodVisitor.visitInsn(Opcodes.AALOAD);
methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
methodVisitor.visitVarInsn(Opcodes.ISTORE, 4);
methodVisitor.visitInsn(Opcodes.ICONST_4);
methodVisitor.visitVarInsn(Opcodes.ISTORE, 5);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 4);
methodVisitor.visitInsn(Opcodes.ICONST_2);
methodVisitor.visitInsn(Opcodes.IAND);
Label markerInterfaceLoop = new Label();
methodVisitor.visitJumpInsn(Opcodes.IFEQ, markerInterfaceLoop);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 5);
methodVisitor.visitIincInsn(5, 1);
methodVisitor.visitInsn(Opcodes.AALOAD);
methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
methodVisitor.visitVarInsn(Opcodes.ISTORE, 7);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 7);
methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
methodVisitor.visitVarInsn(Opcodes.ASTORE, 6);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 5);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 6);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 7);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 5);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 7);
methodVisitor.visitInsn(Opcodes.IADD);
methodVisitor.visitVarInsn(Opcodes.ISTORE, 5);
Label markerInterfaceExit = new Label();
methodVisitor.visitJumpInsn(Opcodes.GOTO, markerInterfaceExit);
methodVisitor.visitLabel(markerInterfaceLoop);
methodVisitor.visitFrame(Opcodes.F_APPEND, 2, new Object[]{Opcodes.INTEGER, Opcodes.INTEGER}, 0, null);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
methodVisitor.visitVarInsn(Opcodes.ASTORE, 6);
methodVisitor.visitLabel(markerInterfaceExit);
methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"[Ljava/lang/Class;"}, 0, null);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 4);
methodVisitor.visitInsn(Opcodes.ICONST_4);
methodVisitor.visitInsn(Opcodes.IAND);
Label additionalBridgesLoop = new Label();
methodVisitor.visitJumpInsn(Opcodes.IFEQ, additionalBridgesLoop);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 5);
methodVisitor.visitIincInsn(5, 1);
methodVisitor.visitInsn(Opcodes.AALOAD);
methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
methodVisitor.visitVarInsn(Opcodes.ISTORE, 8);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 8);
methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/invoke/MethodType");
methodVisitor.visitVarInsn(Opcodes.ASTORE, 7);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 5);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 7);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 8);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false);
Label additionalBridgesExit = new Label();
methodVisitor.visitJumpInsn(Opcodes.GOTO, additionalBridgesExit);
methodVisitor.visitLabel(additionalBridgesLoop);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/invoke/MethodType");
methodVisitor.visitVarInsn(Opcodes.ASTORE, 7);
methodVisitor.visitLabel(additionalBridgesExit);
methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"[Ljava/lang/invoke/MethodType;"}, 0, null);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "sun/misc/Unsafe", "getUnsafe", "()Lsun/misc/Unsafe;", false);
methodVisitor.visitVarInsn(Opcodes.ASTORE, 8);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 8);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "lookupClass", "()Ljava/lang/Class;", false);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false);
methodVisitor.visitLdcInsn("com.fitbur.mockito.bytebuddy.agent.builder.LambdaFactory");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false);
methodVisitor.visitLdcInsn("make");
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9);
methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_1);
methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/String;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_2);
methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_3);
methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_4);
methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_5);
methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6);
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7);
methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8);
methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;"));
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
methodVisitor.visitInsn(Opcodes.ACONST_NULL);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9);
methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_1);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_2);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_3);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitInsn(Opcodes.AALOAD);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_4);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
methodVisitor.visitInsn(Opcodes.ICONST_1);
methodVisitor.visitInsn(Opcodes.AALOAD);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitInsn(Opcodes.ICONST_5);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
methodVisitor.visitInsn(Opcodes.ICONST_2);
methodVisitor.visitInsn(Opcodes.AALOAD);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6);
methodVisitor.visitVarInsn(Opcodes.ILOAD, 4);
methodVisitor.visitInsn(Opcodes.ICONST_1);
methodVisitor.visitInsn(Opcodes.IAND);
Label callSiteConditional = new Label();
methodVisitor.visitJumpInsn(Opcodes.IFEQ, callSiteConditional);
methodVisitor.visitInsn(Opcodes.ICONST_1);
Label callSiteAlternative = new Label();
methodVisitor.visitJumpInsn(Opcodes.GOTO, callSiteAlternative);
methodVisitor.visitLabel(callSiteConditional);
methodVisitor.visitFrame(Opcodes.F_FULL, 9, new Object[]{"java/lang/invoke/MethodHandles$Lookup", "java/lang/String", "java/lang/invoke/MethodType", "[Ljava/lang/Object;", Opcodes.INTEGER, Opcodes.INTEGER, "[Ljava/lang/Class;", "[Ljava/lang/invoke/MethodType;", "sun/misc/Unsafe"}, 7, new Object[]{"sun/misc/Unsafe", "java/lang/Class", "java/lang/reflect/Method", Opcodes.NULL, "[Ljava/lang/Object;", "[Ljava/lang/Object;", Opcodes.INTEGER});
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitLabel(callSiteAlternative);
methodVisitor.visitFrame(Opcodes.F_FULL, 9, new Object[]{"java/lang/invoke/MethodHandles$Lookup", "java/lang/String", "java/lang/invoke/MethodType", "[Ljava/lang/Object;", Opcodes.INTEGER, Opcodes.INTEGER, "[Ljava/lang/Class;", "[Ljava/lang/invoke/MethodType;", "sun/misc/Unsafe"}, 8, new Object[]{"sun/misc/Unsafe", "java/lang/Class", "java/lang/reflect/Method", Opcodes.NULL, "[Ljava/lang/Object;", "[Ljava/lang/Object;", Opcodes.INTEGER, Opcodes.INTEGER});
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 6);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", false);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 7);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", false);
methodVisitor.visitInsn(Opcodes.AASTORE);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false);
methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "[B");
methodVisitor.visitInsn(Opcodes.ACONST_NULL);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "sun/misc/Unsafe", "defineAnonymousClass", "(Ljava/lang/Class;[B[Ljava/lang/Object;)Ljava/lang/Class;", false);
methodVisitor.visitVarInsn(Opcodes.ASTORE, 9);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 8);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 9);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "sun/misc/Unsafe", "ensureClassInitialized", "(Ljava/lang/Class;)V", false);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "parameterCount", "()I", false);
Label callSiteJump = new Label();
methodVisitor.visitJumpInsn(Opcodes.IFNE, callSiteJump);
methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite");
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "returnType", "()Ljava/lang/Class;", false);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 9);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", false);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitInsn(Opcodes.AALOAD);
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Constructor", "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;", false);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandles", "constant", "(Ljava/lang/Class;Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;", false);
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", "(Ljava/lang/invoke/MethodHandle;)V", false);
Label callSiteExit = new Label();
methodVisitor.visitJumpInsn(Opcodes.GOTO, callSiteExit);
methodVisitor.visitLabel(callSiteJump);
methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"java/lang/Class"}, 0, null);
methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite");
methodVisitor.visitInsn(Opcodes.DUP);
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/invoke/MethodHandles$Lookup", "IMPL_LOOKUP", "Ljava/lang/invoke/MethodHandles$Lookup;");
methodVisitor.visitVarInsn(Opcodes.ALOAD, 9);
methodVisitor.visitLdcInsn("get$Lambda");
methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false);
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", "(Ljava/lang/invoke/MethodHandle;)V", false);
methodVisitor.visitLabel(callSiteExit);
methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/invoke/CallSite"});
methodVisitor.visitInsn(Opcodes.ARETURN);
methodVisitor.visitMaxs(9, 10);
methodVisitor.visitEnd();
return IGNORE_ORIGINAL;
}
@Override
public String toString() {
return "AgentBuilder.LambdaInstrumentationStrategy.AlternativeMetaFactoryRedirection." + name();
}
}
}
/**
*
* The default implementation of an {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder}.
*
*
* By default, Byte Buddy ignores any types loaded by the bootstrap class loader and
* any synthetic type. Self-injection and rebasing is enabled. In order to avoid class format changes, set
* {@link AgentBuilder#disableBootstrapInjection()}). All types are parsed without their debugging information ({@link TypeLocator.Default#FAST}).
*
*/
class Default implements AgentBuilder {
/**
* The name of the Byte Buddy {@code com.fitbur.mockito.bytebuddy.agent.Installer} class.
*/
private static final String INSTALLER_TYPE = "com.fitbur.mockito.bytebuddy.agent.Installer";
/**
* The name of the {@code com.fitbur.mockito.bytebuddy.agent.Installer} field containing an installed {@link Instrumentation}.
*/
private static final String INSTRUMENTATION_FIELD = "instrumentation";
/**
* Indicator for access to a static member via reflection to make the code more readable.
*/
private static final Object STATIC_FIELD = null;
/**
* The value that is to be returned from a {@link java.lang.instrument.ClassFileTransformer} to indicate
* that no class file transformation is to be applied.
*/
private static final byte[] NO_TRANSFORMATION = null;
/**
* The {@link com.fitbur.mockito.bytebuddy.ByteBuddy} instance to be used.
*/
private final ByteBuddy byteBuddy;
/**
* The type locator to use.
*/
private final TypeLocator typeLocator;
/**
* The definition handler to use.
*/
private final TypeStrategy typeStrategy;
/**
* The listener to notify on transformations.
*/
private final Listener listener;
/**
* The native method strategy to use.
*/
private final NativeMethodStrategy nativeMethodStrategy;
/**
* The access control context to use for loading classes.
*/
private final AccessControlContext accessControlContext;
/**
* The initialization strategy to use for creating classes.
*/
private final InitializationStrategy initializationStrategy;
/**
* The redefinition strategy to apply.
*/
private final RedefinitionStrategy redefinitionStrategy;
/**
* The injection strategy for injecting classes into the bootstrap class loader.
*/
private final BootstrapInjectionStrategy bootstrapInjectionStrategy;
/**
* A strategy to determine of the {@code LambdaMetfactory} should be instrumented to allow for the instrumentation
* of classes that represent lambda expressions.
*/
private final LambdaInstrumentationStrategy lambdaInstrumentationStrategy;
/**
* The description strategy for resolving type descriptions for types.
*/
private final DescriptionStrategy descriptionStrategy;
/**
* Identifies types that should not be instrumented.
*/
private final RawMatcher ignoredTypeMatcher;
/**
* The transformation object for handling type transformations.
*/
private final Transformation transformation;
/**
* Creates a new default agent builder that uses a default {@link com.fitbur.mockito.bytebuddy.ByteBuddy} instance for
* creating classes.
*
* @see Default#Default(ByteBuddy)
*/
public Default() {
this(new ByteBuddy());
}
/**
* Creates a new agent builder with default settings. By default, Byte Buddy ignores any types loaded by the bootstrap class loader, any
* type within a {@code com.fitbur.mockito.bytebuddy} package and any synthetic type. Self-injection and rebasing is enabled. In order to avoid class format
* changes, set {@link AgentBuilder#disableBootstrapInjection()}). All types are parsed without their debugging information
* ({@link TypeLocator.Default#FAST}).
*
* @param byteBuddy The Byte Buddy instance to be used.
*/
public Default(ByteBuddy byteBuddy) {
this(byteBuddy,
TypeLocator.Default.FAST,
TypeStrategy.Default.REBASE,
Listener.NoOp.INSTANCE,
NativeMethodStrategy.Disabled.INSTANCE,
AccessController.getContext(),
InitializationStrategy.SelfInjection.SPLIT,
RedefinitionStrategy.DISABLED,
BootstrapInjectionStrategy.Disabled.INSTANCE,
LambdaInstrumentationStrategy.DISABLED,
DescriptionStrategy.Default.HYBRID,
new RawMatcher.Disjunction(new RawMatcher.ForElementMatchers(any(), isBootstrapClassLoader(), any()), new RawMatcher.ForElementMatchers(nameStartsWith("com.fitbur.mockito.bytebuddy.").or(isSynthetic()), any(), any())),
Transformation.Ignored.INSTANCE);
}
/**
* Creates a new default agent builder.
*
* @param byteBuddy The Byte Buddy instance to be used.
* @param typeLocator The type locator to use.
* @param typeStrategy The definition handler to use.
* @param listener The listener to notify on transformations.
* @param nativeMethodStrategy The native method strategy to apply.
* @param accessControlContext The access control context to use for loading classes.
* @param initializationStrategy The initialization strategy to use for transformed types.
* @param redefinitionStrategy The redefinition strategy to apply.
* @param bootstrapInjectionStrategy The injection strategy for injecting classes into the bootstrap class loader.
* @param lambdaInstrumentationStrategy A strategy to determine of the {@code LambdaMetfactory} should be instrumented to allow for the
* instrumentation of classes that represent lambda expressions.
* @param descriptionStrategy The description strategy for resolving type descriptions for types.
* @param ignoredTypeMatcher Identifies types that should not be instrumented.
* @param transformation The transformation object for handling type transformations.
*/
protected Default(ByteBuddy byteBuddy,
TypeLocator typeLocator,
TypeStrategy typeStrategy,
Listener listener,
NativeMethodStrategy nativeMethodStrategy,
AccessControlContext accessControlContext,
InitializationStrategy initializationStrategy,
RedefinitionStrategy redefinitionStrategy,
BootstrapInjectionStrategy bootstrapInjectionStrategy,
LambdaInstrumentationStrategy lambdaInstrumentationStrategy,
DescriptionStrategy descriptionStrategy,
RawMatcher ignoredTypeMatcher,
Transformation transformation) {
this.byteBuddy = byteBuddy;
this.typeLocator = typeLocator;
this.typeStrategy = typeStrategy;
this.listener = listener;
this.nativeMethodStrategy = nativeMethodStrategy;
this.accessControlContext = accessControlContext;
this.initializationStrategy = initializationStrategy;
this.redefinitionStrategy = redefinitionStrategy;
this.bootstrapInjectionStrategy = bootstrapInjectionStrategy;
this.lambdaInstrumentationStrategy = lambdaInstrumentationStrategy;
this.descriptionStrategy = descriptionStrategy;
this.ignoredTypeMatcher = ignoredTypeMatcher;
this.transformation = transformation;
}
@Override
public AgentBuilder with(ByteBuddy byteBuddy) {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder with(Listener listener) {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
new Listener.Compound(this.listener, listener),
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder with(TypeStrategy typeStrategy) {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder with(TypeLocator typeLocator) {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder enableNativeMethodPrefix(String prefix) {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
NativeMethodStrategy.ForPrefix.of(prefix),
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder disableNativeMethodPrefix() {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
NativeMethodStrategy.Disabled.INSTANCE,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder with(AccessControlContext accessControlContext) {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder with(RedefinitionStrategy redefinitionStrategy) {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder with(InitializationStrategy initializationStrategy) {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder with(LambdaInstrumentationStrategy lambdaInstrumentationStrategy) {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder with(DescriptionStrategy descriptionStrategy) {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder enableBootstrapInjection(Instrumentation instrumentation, File folder) {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
new BootstrapInjectionStrategy.Enabled(folder, instrumentation),
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder disableBootstrapInjection() {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
BootstrapInjectionStrategy.Disabled.INSTANCE,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder disableClassFormatChanges() {
return new Default(byteBuddy.with(Implementation.Context.Disabled.Factory.INSTANCE),
typeLocator,
TypeStrategy.Default.REDEFINE_DECLARED_ONLY,
listener,
nativeMethodStrategy,
accessControlContext,
InitializationStrategy.NoOp.INSTANCE,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Class>... type) {
return JavaModule.isSupported()
? with(Listener.ModuleReadEdgeCompleting.of(instrumentation, false, type))
: this;
}
@Override
public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, JavaModule... module) {
return assureReadEdgeTo(instrumentation, Arrays.asList(module));
}
@Override
public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Collection extends JavaModule> modules) {
return with(new Listener.ModuleReadEdgeCompleting(instrumentation, false, new HashSet(modules)));
}
@Override
public AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Class>... type) {
return JavaModule.isSupported()
? with(Listener.ModuleReadEdgeCompleting.of(instrumentation, true, type))
: this;
}
@Override
public AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, JavaModule... module) {
return assureReadEdgeFromAndTo(instrumentation, Arrays.asList(module));
}
@Override
public AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Collection extends JavaModule> modules) {
return with(new Listener.ModuleReadEdgeCompleting(instrumentation, true, new HashSet(modules)));
}
@Override
public Identified.Narrowable type(RawMatcher matcher) {
return new Transforming(matcher, Transformer.NoOp.INSTANCE, false);
}
@Override
public Identified.Narrowable type(ElementMatcher super TypeDescription> typeMatcher) {
return type(typeMatcher, any());
}
@Override
public Identified.Narrowable type(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher) {
return type(typeMatcher, classLoaderMatcher, any());
}
@Override
public Identified.Narrowable type(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher) {
return type(new RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, not(supportsModules()).or(moduleMatcher)));
}
@Override
public Ignored ignore(ElementMatcher super TypeDescription> typeMatcher) {
return ignore(typeMatcher, any());
}
@Override
public Ignored ignore(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher) {
return ignore(typeMatcher, classLoaderMatcher, any());
}
@Override
public Ignored ignore(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher) {
return ignore(new RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, not(supportsModules()).or(moduleMatcher)));
}
@Override
public Ignored ignore(RawMatcher rawMatcher) {
return new Ignoring(rawMatcher);
}
@Override
public ClassFileTransformer makeRaw() {
return ExecutingTransformer.FACTORY.make(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
bootstrapInjectionStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public ClassFileTransformer installOn(Instrumentation instrumentation) {
ClassFileTransformer classFileTransformer = makeRaw();
instrumentation.addTransformer(classFileTransformer, redefinitionStrategy.isRetransforming(instrumentation));
if (nativeMethodStrategy.isEnabled(instrumentation)) {
instrumentation.setNativeMethodPrefix(classFileTransformer, nativeMethodStrategy.getPrefix());
}
lambdaInstrumentationStrategy.apply(byteBuddy, instrumentation, classFileTransformer);
if (redefinitionStrategy.isEnabled()) {
RedefinitionStrategy.Collector collector = redefinitionStrategy.makeCollector(transformation);
for (Class> type : instrumentation.getAllLoadedClasses()) {
TypeDescription typeDescription = descriptionStrategy.apply(type, typeLocator);
JavaModule module = JavaModule.ofType(type);
try {
if (!instrumentation.isModifiableClass(type) || !collector.consider(typeDescription, type, ignoredTypeMatcher)) {
try {
try {
listener.onIgnored(typeDescription, type.getClassLoader(), module);
} finally {
listener.onComplete(typeDescription.getName(), type.getClassLoader(), module);
}
} catch (Throwable ignored) {
// Ignore exceptions that are thrown by listeners to mimic the behavior of a transformation.
}
}
} catch (Throwable throwable) {
try {
try {
listener.onError(typeDescription.getName(), type.getClassLoader(), module, throwable);
} finally {
listener.onComplete(typeDescription.getName(), type.getClassLoader(), module);
}
} catch (Throwable ignored) {
// Ignore exceptions that are thrown by listeners to mimic the behavior of a transformation.
}
}
}
try {
collector.apply(instrumentation, typeLocator, listener);
} catch (UnmodifiableClassException exception) {
throw new IllegalStateException("Cannot modify at least one class: " + collector, exception);
} catch (ClassNotFoundException exception) {
throw new IllegalStateException("Cannot find at least one class: " + collector, exception);
}
}
return classFileTransformer;
}
@Override
public ClassFileTransformer installOnByteBuddyAgent() {
try {
Instrumentation instrumentation = (Instrumentation) ClassLoader.getSystemClassLoader()
.loadClass(INSTALLER_TYPE)
.getDeclaredField(INSTRUMENTATION_FIELD)
.get(STATIC_FIELD);
if (instrumentation == null) {
throw new IllegalStateException("The Byte Buddy agent is not installed");
}
return installOn(instrumentation);
} catch (RuntimeException exception) {
throw exception;
} catch (Exception exception) {
throw new IllegalStateException("The Byte Buddy agent is not installed or not accessible", exception);
}
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Default aDefault = (Default) other;
return typeLocator.equals(aDefault.typeLocator)
&& byteBuddy.equals(aDefault.byteBuddy)
&& listener.equals(aDefault.listener)
&& nativeMethodStrategy.equals(aDefault.nativeMethodStrategy)
&& typeStrategy.equals(aDefault.typeStrategy)
&& accessControlContext.equals(aDefault.accessControlContext)
&& initializationStrategy == aDefault.initializationStrategy
&& redefinitionStrategy == aDefault.redefinitionStrategy
&& bootstrapInjectionStrategy.equals(aDefault.bootstrapInjectionStrategy)
&& lambdaInstrumentationStrategy.equals(aDefault.lambdaInstrumentationStrategy)
&& descriptionStrategy == aDefault.descriptionStrategy
&& ignoredTypeMatcher.equals(aDefault.ignoredTypeMatcher)
&& transformation.equals(aDefault.transformation);
}
@Override
public int hashCode() {
int result = byteBuddy.hashCode();
result = 31 * result + typeLocator.hashCode();
result = 31 * result + listener.hashCode();
result = 31 * result + typeStrategy.hashCode();
result = 31 * result + nativeMethodStrategy.hashCode();
result = 31 * result + accessControlContext.hashCode();
result = 31 * result + initializationStrategy.hashCode();
result = 31 * result + redefinitionStrategy.hashCode();
result = 31 * result + bootstrapInjectionStrategy.hashCode();
result = 31 * result + lambdaInstrumentationStrategy.hashCode();
result = 31 * result + descriptionStrategy.hashCode();
result = 31 * result + ignoredTypeMatcher.hashCode();
result = 31 * result + transformation.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.Default{" +
"byteBuddy=" + byteBuddy +
", typeLocator=" + typeLocator +
", typeStrategy=" + typeStrategy +
", listener=" + listener +
", nativeMethodStrategy=" + nativeMethodStrategy +
", accessControlContext=" + accessControlContext +
", initializationStrategy=" + initializationStrategy +
", redefinitionStrategy=" + redefinitionStrategy +
", bootstrapInjectionStrategy=" + bootstrapInjectionStrategy +
", lambdaInstrumentationStrategy=" + lambdaInstrumentationStrategy +
", descriptionStrategy=" + descriptionStrategy +
", ignoredTypeMatcher=" + ignoredTypeMatcher +
", transformation=" + transformation +
'}';
}
/**
* An injection strategy for injecting classes into the bootstrap class loader.
*/
protected interface BootstrapInjectionStrategy {
/**
* Creates an injector for the bootstrap class loader.
*
* @param protectionDomain The protection domain to be used.
* @return A class injector for the bootstrap class loader.
*/
ClassInjector make(ProtectionDomain protectionDomain);
/**
* A disabled bootstrap injection strategy.
*/
enum Disabled implements BootstrapInjectionStrategy {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public ClassInjector make(ProtectionDomain protectionDomain) {
throw new IllegalStateException("Injecting classes into the bootstrap class loader was not enabled");
}
@Override
public String toString() {
return "AgentBuilder.Default.BootstrapInjectionStrategy.Disabled." + name();
}
}
/**
* An enabled bootstrap injection strategy.
*/
class Enabled implements BootstrapInjectionStrategy {
/**
* The folder in which jar files are to be saved.
*/
private final File folder;
/**
* The instrumentation to use for appending jar files.
*/
private final Instrumentation instrumentation;
/**
* Creates a new enabled bootstrap class loader injection strategy.
*
* @param folder The folder in which jar files are to be saved.
* @param instrumentation The instrumentation to use for appending jar files.
*/
public Enabled(File folder, Instrumentation instrumentation) {
this.folder = folder;
this.instrumentation = instrumentation;
}
@Override
public ClassInjector make(ProtectionDomain protectionDomain) {
return ClassInjector.UsingInstrumentation.of(folder, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, instrumentation);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Enabled enabled = (Enabled) other;
return folder.equals(enabled.folder) && instrumentation.equals(enabled.instrumentation);
}
@Override
public int hashCode() {
int result = folder.hashCode();
result = 31 * result + instrumentation.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.Default.BootstrapInjectionStrategy.Enabled{" +
"folder=" + folder +
", instrumentation=" + instrumentation +
'}';
}
}
}
/**
* A strategy for determining if a native method name prefix should be used when rebasing methods.
*/
protected interface NativeMethodStrategy {
/**
* Determines if this strategy enables name prefixing for native methods.
*
* @param instrumentation The instrumentation used.
* @return {@code true} if this strategy indicates that a native method prefix should be used.
*/
boolean isEnabled(Instrumentation instrumentation);
/**
* Resolves the method name transformer for this strategy.
*
* @return A method name transformer for this strategy.
*/
MethodNameTransformer resolve();
/**
* Returns the method prefix if the strategy is enabled. This method must only be called if this strategy enables prefixing.
*
* @return The method prefix.
*/
String getPrefix();
/**
* A native method strategy that suffixes method names with a random suffix and disables native method rebasement.
*/
enum Disabled implements NativeMethodStrategy {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public MethodNameTransformer resolve() {
return MethodNameTransformer.Suffixing.withRandomSuffix();
}
@Override
public boolean isEnabled(Instrumentation instrumentation) {
return false;
}
@Override
public String getPrefix() {
throw new IllegalStateException("A disabled native method strategy does not define a method name prefix");
}
@Override
public String toString() {
return "AgentBuilder.Default.NativeMethodStrategy.Disabled." + name();
}
}
/**
* A native method strategy that prefixes method names with a fixed value for supporting rebasing of native methods.
*/
class ForPrefix implements NativeMethodStrategy {
/**
* The method name prefix.
*/
private final String prefix;
/**
* Creates a new name prefixing native method strategy.
*
* @param prefix The method name prefix.
*/
protected ForPrefix(String prefix) {
this.prefix = prefix;
}
/**
* Creates a new native method strategy for prefixing method names.
*
* @param prefix The method name prefix.
* @return An appropriate native method strategy.
*/
protected static NativeMethodStrategy of(String prefix) {
if (prefix.length() == 0) {
throw new IllegalArgumentException("A method name prefix must not be the empty string");
}
return new ForPrefix(prefix);
}
@Override
public MethodNameTransformer resolve() {
return new MethodNameTransformer.Prefixing(prefix);
}
@Override
public boolean isEnabled(Instrumentation instrumentation) {
if (!instrumentation.isNativeMethodPrefixSupported()) {
throw new IllegalArgumentException("A prefix for native methods is not supported: " + instrumentation);
}
return true;
}
@Override
public String getPrefix() {
return prefix;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass()) && prefix.equals(((ForPrefix) other).prefix);
}
@Override
public int hashCode() {
return prefix.hashCode();
}
@Override
public String toString() {
return "AgentBuilder.Default.NativeMethodStrategy.ForPrefix{" +
"prefix='" + prefix + '\'' +
'}';
}
}
}
/**
* A transformation serves as a handler for modifying a class.
*/
protected interface Transformation {
/**
* Resolves an attempted transformation to a specific transformation.
*
* @param typeDescription A description of the type that is to be transformed.
* @param classLoader The class loader of the type being transformed.
* @param module The transformed type's module or {@code null} if the current VM does not support modules.
* @param classBeingRedefined In case of a type redefinition, the loaded type being transformed or {@code null} if that is not the case.
* @param protectionDomain The protection domain of the type being transformed.
* @param ignoredTypeMatcher Identifies types that should not be instrumented.
* @return A resolution for the given type.
*/
Resolution resolve(TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module,
Class> classBeingRedefined,
ProtectionDomain protectionDomain,
RawMatcher ignoredTypeMatcher);
/**
* A resolution to a transformation.
*/
interface Resolution {
/**
* Returns the sort of this resolution.
*
* @return The sort of this resolution.
*/
Sort getSort();
/**
* Resolves this resolution as a decorator of the supplied resolution.
*
* @param resolution The resolution for which this resolution should serve as a decorator.
* @return A resolution where this resolution is applied as a decorator if this resolution is alive.
*/
Resolution asDecoratorOf(Resolution resolution);
/**
* Resolves this resolution as a decorator of the supplied resolution.
*
* @param resolution The resolution for which this resolution should serve as a decorator.
* @return A resolution where this resolution is applied as a decorator if this resolution is alive.
*/
Resolution prepend(Decoratable resolution);
/**
* Transforms a type or returns {@code null} if a type is not to be transformed.
*
* @param initializationStrategy The initialization strategy to use.
* @param classFileLocator The class file locator to use.
* @param typeStrategy The definition handler to use.
* @param byteBuddy The Byte Buddy instance to use.
* @param methodNameTransformer The method name transformer to be used.
* @param bootstrapInjectionStrategy The bootstrap injection strategy to be used.
* @param accessControlContext The access control context to be used.
* @param listener The listener to be invoked to inform about an applied or non-applied transformation.
* @return The class file of the transformed class or {@code null} if no transformation is attempted.
*/
byte[] apply(InitializationStrategy initializationStrategy,
ClassFileLocator classFileLocator,
TypeStrategy typeStrategy,
ByteBuddy byteBuddy,
NativeMethodStrategy methodNameTransformer,
BootstrapInjectionStrategy bootstrapInjectionStrategy,
AccessControlContext accessControlContext,
Listener listener);
/**
* Describes a specific sort of a {@link Resolution}.
*/
enum Sort {
/**
* A terminal resolution. After discovering such a resolution, no further transformers are considered.
*/
TERMINAL(true),
/**
* A resolution that can serve as a decorator for another resolution. After discovering such a resolution
* further transformations are considered where the represented resolution is prepended if applicable.
*/
DECORATOR(true),
/**
* A non-resolved resolution.
*/
UNDEFINED(false);
/**
* Indicates if this sort represents an active resolution.
*/
private final boolean alive;
/**
* Creates a new resolution sort.
*
* @param alive Indicates if this sort represents an active resolution.
*/
Sort(boolean alive) {
this.alive = alive;
}
/**
* Returns {@code true} if this resolution is alive.
*
* @return {@code true} if this resolution is alive.
*/
protected boolean isAlive() {
return alive;
}
@Override
public String toString() {
return "AgentBuilder.Default.Transformation.Resolution.Sort." + name();
}
}
/**
* A resolution that can be decorated by a transformer.
*/
interface Decoratable extends Resolution {
/**
* Appends the supplied transformer to this resolution.
*
* @param transformer The transformer to append to the transformer that is represented bz this instance.
* @return A new resolution with the supplied transformer appended to this transformer.
*/
Resolution append(Transformer transformer);
}
/**
* A canonical implementation of a non-resolved resolution.
*/
class Unresolved implements Resolution {
/**
* The type that is not transformed.
*/
private final TypeDescription typeDescription;
/**
* The unresolved type's class loader.
*/
private final ClassLoader classLoader;
/**
* The non-transformed type's module or {@code null} if the current VM does not support modules.
*/
private final JavaModule module;
/**
* Creates a new unresolved resolution.
*
* @param typeDescription The type that is not transformed.
* @param classLoader The unresolved type's class loader.
* @param module The non-transformed type's module or {@code null} if the current VM does not support modules.
*/
protected Unresolved(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
this.typeDescription = typeDescription;
this.classLoader = classLoader;
this.module = module;
}
@Override
public Sort getSort() {
return Sort.UNDEFINED;
}
@Override
public Resolution asDecoratorOf(Resolution resolution) {
return resolution;
}
@Override
public Resolution prepend(Decoratable resolution) {
return resolution;
}
@Override
public byte[] apply(InitializationStrategy initializationStrategy,
ClassFileLocator classFileLocator,
TypeStrategy typeStrategy,
ByteBuddy byteBuddy,
NativeMethodStrategy methodNameTransformer,
BootstrapInjectionStrategy bootstrapInjectionStrategy,
AccessControlContext accessControlContext,
Listener listener) {
listener.onIgnored(typeDescription, classLoader, module);
return NO_TRANSFORMATION;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
Unresolved that = (Unresolved) object;
return typeDescription.equals(that.typeDescription)
&& (classLoader != null ? classLoader.equals(that.classLoader) : that.classLoader == null)
&& (module != null ? module.equals(that.module) : that.module == null);
}
@Override
public int hashCode() {
int result = typeDescription.hashCode();
result = 31 * result + (classLoader != null ? classLoader.hashCode() : 0);
result = 31 * result + (module != null ? module.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "AgentBuilder.Default.Transformation.Resolution.Unresolved{" +
"typeDescription=" + typeDescription +
", classLoader=" + classLoader +
", module=" + module +
'}';
}
}
}
/**
* A transformation that does not attempt to transform any type.
*/
enum Ignored implements Transformation {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public Resolution resolve(TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module,
Class> classBeingRedefined,
ProtectionDomain protectionDomain,
RawMatcher ignoredTypeMatcher) {
return new Resolution.Unresolved(typeDescription, classLoader, module);
}
@Override
public String toString() {
return "AgentBuilder.Default.Transformation.Ignored." + name();
}
}
/**
* A simple, active transformation.
*/
class Simple implements Transformation {
/**
* The raw matcher that is represented by this transformation.
*/
private final RawMatcher rawMatcher;
/**
* The transformer that is represented by this transformation.
*/
private final Transformer transformer;
/**
* {@code true} if this transformer serves as a decorator.
*/
private final boolean decorator;
/**
* Creates a new transformation.
*
* @param rawMatcher The raw matcher that is represented by this transformation.
* @param transformer The transformer that is represented by this transformation.
* @param decorator {@code true} if this transformer serves as a decorator.
*/
protected Simple(RawMatcher rawMatcher, Transformer transformer, boolean decorator) {
this.rawMatcher = rawMatcher;
this.transformer = transformer;
this.decorator = decorator;
}
@Override
public Transformation.Resolution resolve(TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module,
Class> classBeingRedefined,
ProtectionDomain protectionDomain,
RawMatcher ignoredTypeMatcher) {
return !ignoredTypeMatcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)
&& rawMatcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)
? new Resolution(typeDescription, classLoader, module, protectionDomain, transformer, decorator)
: new Transformation.Resolution.Unresolved(typeDescription, classLoader, module);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& decorator == ((Simple) other).decorator
&& rawMatcher.equals(((Simple) other).rawMatcher)
&& transformer.equals(((Simple) other).transformer);
}
@Override
public int hashCode() {
int result = rawMatcher.hashCode();
result = 31 * result + (decorator ? 1 : 0);
result = 31 * result + transformer.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.Default.Transformation.Simple{" +
"rawMatcher=" + rawMatcher +
", transformer=" + transformer +
", decorator=" + decorator +
'}';
}
/**
* A resolution that performs a type transformation.
*/
protected static class Resolution implements Transformation.Resolution.Decoratable {
/**
* A description of the transformed type.
*/
private final TypeDescription typeDescription;
/**
* The class loader of the transformed type.
*/
private final ClassLoader classLoader;
/**
* The transformed type's module or {@code null} if the current VM does not support modules.
*/
private final JavaModule module;
/**
* The protection domain of the transformed type.
*/
private final ProtectionDomain protectionDomain;
/**
* The transformer to be applied.
*/
private final Transformer transformer;
/**
* {@code true} if this transformer serves as a decorator.
*/
private final boolean decorator;
/**
* Creates a new active transformation.
*
* @param typeDescription A description of the transformed type.
* @param classLoader The class loader of the transformed type.
* @param module The transformed type's module or {@code null} if the current VM does not support modules.
* @param protectionDomain The protection domain of the transformed type.
* @param transformer The transformer to be applied.
* @param decorator {@code true} if this transformer serves as a decorator.
*/
protected Resolution(TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module,
ProtectionDomain protectionDomain,
Transformer transformer,
boolean decorator) {
this.typeDescription = typeDescription;
this.classLoader = classLoader;
this.module = module;
this.protectionDomain = protectionDomain;
this.transformer = transformer;
this.decorator = decorator;
}
@Override
public Sort getSort() {
return decorator
? Sort.DECORATOR
: Sort.TERMINAL;
}
@Override
public Transformation.Resolution asDecoratorOf(Transformation.Resolution resolution) {
return resolution.prepend(this);
}
@Override
public Transformation.Resolution prepend(Decoratable resolution) {
return resolution.append(transformer);
}
@Override
public Transformation.Resolution append(Transformer transformer) {
return new Resolution(typeDescription,
classLoader,
module,
protectionDomain,
new Transformer.Compound(this.transformer, transformer),
decorator);
}
@Override
public byte[] apply(InitializationStrategy initializationStrategy,
ClassFileLocator classFileLocator,
TypeStrategy typeStrategy,
ByteBuddy byteBuddy,
NativeMethodStrategy methodNameTransformer,
BootstrapInjectionStrategy bootstrapInjectionStrategy,
AccessControlContext accessControlContext,
Listener listener) {
InitializationStrategy.Dispatcher dispatcher = initializationStrategy.dispatcher();
DynamicType.Unloaded> dynamicType = dispatcher.apply(transformer.transform(typeStrategy.builder(typeDescription,
byteBuddy,
classFileLocator,
methodNameTransformer.resolve()), typeDescription, classLoader)).make();
dispatcher.register(dynamicType, classLoader, new BootstrapClassLoaderCapableInjectorFactory(bootstrapInjectionStrategy,
classLoader,
protectionDomain,
accessControlContext));
listener.onTransformation(typeDescription, classLoader, module, dynamicType);
return dynamicType.getBytes();
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Resolution that = (Resolution) other;
return typeDescription.equals(that.typeDescription)
&& decorator == that.decorator
&& !(classLoader != null ? !classLoader.equals(that.classLoader) : that.classLoader != null)
&& !(module != null ? !module.equals(that.module) : that.module != null)
&& !(protectionDomain != null ? !protectionDomain.equals(that.protectionDomain) : that.protectionDomain != null)
&& transformer.equals(that.transformer);
}
@Override
public int hashCode() {
int result = typeDescription.hashCode();
result = 31 * result + (decorator ? 1 : 0);
result = 31 * result + (classLoader != null ? classLoader.hashCode() : 0);
result = 31 * result + (module != null ? module.hashCode() : 0);
result = 31 * result + (protectionDomain != null ? protectionDomain.hashCode() : 0);
result = 31 * result + transformer.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.Default.Transformation.Simple.Resolution{" +
"typeDescription=" + typeDescription +
", classLoader=" + classLoader +
", module=" + module +
", protectionDomain=" + protectionDomain +
", transformer=" + transformer +
", decorator=" + decorator +
'}';
}
/**
* An injector factory that resolves to a bootstrap class loader injection if this is necessary and enabled.
*/
protected static class BootstrapClassLoaderCapableInjectorFactory implements InitializationStrategy.Dispatcher.InjectorFactory {
/**
* The bootstrap injection strategy being used.
*/
private final BootstrapInjectionStrategy bootstrapInjectionStrategy;
/**
* The class loader for which to create an injection factory.
*/
private final ClassLoader classLoader;
/**
* The protection domain of the created classes.
*/
private final ProtectionDomain protectionDomain;
/**
* The access control context to be used.
*/
private final AccessControlContext accessControlContext;
/**
* Creates a new bootstrap class loader capable injector factory.
*
* @param bootstrapInjectionStrategy The bootstrap injection strategy being used.
* @param classLoader The class loader for which to create an injection factory.
* @param protectionDomain The protection domain of the created classes.
* @param accessControlContext The access control context to be used.
*/
protected BootstrapClassLoaderCapableInjectorFactory(BootstrapInjectionStrategy bootstrapInjectionStrategy,
ClassLoader classLoader,
ProtectionDomain protectionDomain,
AccessControlContext accessControlContext) {
this.bootstrapInjectionStrategy = bootstrapInjectionStrategy;
this.classLoader = classLoader;
this.protectionDomain = protectionDomain;
this.accessControlContext = accessControlContext;
}
@Override
public ClassInjector resolve() {
return classLoader == null
? bootstrapInjectionStrategy.make(protectionDomain)
: new ClassInjector.UsingReflection(classLoader, protectionDomain, accessControlContext);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
BootstrapClassLoaderCapableInjectorFactory that = (BootstrapClassLoaderCapableInjectorFactory) other;
return bootstrapInjectionStrategy.equals(that.bootstrapInjectionStrategy)
&& !(classLoader != null ? !classLoader.equals(that.classLoader) : that.classLoader != null)
&& !(protectionDomain != null ? !protectionDomain.equals(that.protectionDomain) : that.protectionDomain != null)
&& accessControlContext.equals(that.accessControlContext);
}
@Override
public int hashCode() {
int result = bootstrapInjectionStrategy.hashCode();
result = 31 * result + (protectionDomain != null ? protectionDomain.hashCode() : 0);
result = 31 * result + (classLoader != null ? classLoader.hashCode() : 0);
result = 31 * result + accessControlContext.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.Default.Transformation.Simple.Resolution.BootstrapClassLoaderCapableInjectorFactory{" +
"bootstrapInjectionStrategy=" + bootstrapInjectionStrategy +
", classLoader=" + classLoader +
", protectionDomain=" + protectionDomain +
", accessControlContext=" + accessControlContext +
'}';
}
}
}
}
/**
* A compound transformation that applied several transformation in the given order and applies the first active transformation.
*/
class Compound implements Transformation {
/**
* The list of transformations to apply in their application order.
*/
private final List extends Transformation> transformations;
/**
* Creates a new compound transformation.
*
* @param transformation An array of transformations to apply in their application order.
*/
protected Compound(Transformation... transformation) {
this(Arrays.asList(transformation));
}
/**
* Creates a new compound transformation.
*
* @param transformations A list of transformations to apply in their application order.
*/
protected Compound(List extends Transformation> transformations) {
this.transformations = transformations;
}
@Override
public Resolution resolve(TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module,
Class> classBeingRedefined,
ProtectionDomain protectionDomain,
RawMatcher ignoredTypeMatcher) {
Resolution current = new Resolution.Unresolved(typeDescription, classLoader, module);
for (Transformation transformation : transformations) {
Resolution resolution = transformation.resolve(typeDescription,
classLoader,
module,
classBeingRedefined,
protectionDomain,
ignoredTypeMatcher);
switch (resolution.getSort()) {
case TERMINAL:
return current.asDecoratorOf(resolution);
case DECORATOR:
current = current.asDecoratorOf(resolution);
break;
case UNDEFINED:
break;
default:
throw new IllegalStateException("Unexpected resolution type: " + resolution.getSort());
}
}
return current;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& transformations.equals(((Compound) other).transformations);
}
@Override
public int hashCode() {
return transformations.hashCode();
}
@Override
public String toString() {
return "AgentBuilder.Default.Transformation.Compound{" +
"transformations=" + transformations +
'}';
}
}
}
/**
* A {@link java.lang.instrument.ClassFileTransformer} that implements the enclosing agent builder's
* configuration.
*/
protected static class ExecutingTransformer implements ClassFileTransformer {
/**
* A factory for creating a {@link ClassFileTransformer} that supports the features of the current VM.
*/
protected static final Factory FACTORY;
/*
* Creates a factory for a class file transformer that supports the features of the current VM.
*/
static {
Factory factory;
try {
factory = new Factory.ForJava9CapableVm(new ByteBuddy()
.subclass(ExecutingTransformer.class)
.method(named("transform").and(takesArgument(0, JavaType.MODULE.getTypeStub())))
.intercept(MethodCall.invoke(ExecutingTransformer.class.getDeclaredMethod("transform",
Object.class,
String.class,
Class.class,
ProtectionDomain.class,
byte[].class)).onSuper().withAllArguments())
.make()
.load(ExecutingTransformer.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded()
.getDeclaredConstructor(ByteBuddy.class,
TypeLocator.class,
TypeStrategy.class,
Listener.class,
NativeMethodStrategy.class,
AccessControlContext.class,
InitializationStrategy.class,
BootstrapInjectionStrategy.class,
DescriptionStrategy.class,
RawMatcher.class,
Transformation.class));
} catch (RuntimeException exception) {
throw exception;
} catch (Exception ignored) {
factory = Factory.ForLegacyVm.INSTANCE;
}
FACTORY = factory;
}
/**
* The Byte Buddy instance to be used.
*/
private final ByteBuddy byteBuddy;
/**
* The type locator to use.
*/
private final TypeLocator typeLocator;
/**
* The definition handler to use.
*/
private final TypeStrategy typeStrategy;
/**
* The listener to notify on transformations.
*/
private final Listener listener;
/**
* The native method strategy to apply.
*/
private final NativeMethodStrategy nativeMethodStrategy;
/**
* The access control context to use for loading classes.
*/
private final AccessControlContext accessControlContext;
/**
* The initialization strategy to use for transformed types.
*/
private final InitializationStrategy initializationStrategy;
/**
* The injection strategy for injecting classes into the bootstrap class loader.
*/
private final BootstrapInjectionStrategy bootstrapInjectionStrategy;
/**
* The description strategy for resolving type descriptions for types.
*/
private final DescriptionStrategy descriptionStrategy;
/**
* Identifies types that should not be instrumented.
*/
private final RawMatcher ignoredTypeMatcher;
/**
* The transformation object for handling type transformations.
*/
private final Transformation transformation;
/**
* Creates a new class file transformer.
*
* @param byteBuddy The Byte Buddy instance to be used.
* @param typeLocator The type locator to use.
* @param typeStrategy The definition handler to use.
* @param listener The listener to notify on transformations.
* @param nativeMethodStrategy The native method strategy to apply.
* @param accessControlContext The access control context to use for loading classes.
* @param initializationStrategy The initialization strategy to use for transformed types.
* @param bootstrapInjectionStrategy The injection strategy for injecting classes into the bootstrap class loader.
* @param descriptionStrategy The description strategy for resolving type descriptions for types.
* @param ignoredTypeMatcher Identifies types that should not be instrumented.
* @param transformation The transformation object for handling type transformations.
*/
public ExecutingTransformer(ByteBuddy byteBuddy,
TypeLocator typeLocator,
TypeStrategy typeStrategy,
Listener listener,
NativeMethodStrategy nativeMethodStrategy,
AccessControlContext accessControlContext,
InitializationStrategy initializationStrategy,
BootstrapInjectionStrategy bootstrapInjectionStrategy,
DescriptionStrategy descriptionStrategy,
RawMatcher ignoredTypeMatcher,
Transformation transformation) {
this.byteBuddy = byteBuddy;
this.typeLocator = typeLocator;
this.typeStrategy = typeStrategy;
this.listener = listener;
this.nativeMethodStrategy = nativeMethodStrategy;
this.accessControlContext = accessControlContext;
this.initializationStrategy = initializationStrategy;
this.bootstrapInjectionStrategy = bootstrapInjectionStrategy;
this.descriptionStrategy = descriptionStrategy;
this.ignoredTypeMatcher = ignoredTypeMatcher;
this.transformation = transformation;
}
@Override
public byte[] transform(ClassLoader classLoader,
String internalTypeName,
Class> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] binaryRepresentation) {
return transform(JavaModule.UNSUPPORTED, classLoader, internalTypeName, classBeingRedefined, protectionDomain, binaryRepresentation);
}
/**
* Applies a transformation for a class that was captured by this {@link ClassFileTransformer}.
*
* @param rawModule The instrumented class's Java {@code java.lang.reflect.Module}.
* @param internalTypeName The internal name of the instrumented class.
* @param classBeingRedefined The loaded {@link Class} being redefined or {@code null} if no such class exists.
* @param protectionDomain The instrumented type's protection domain.
* @param binaryRepresentation The class file of the instrumented class in its current state.
* @return The transformed class file or an empty byte array if this transformer does not apply an instrumentation.
*/
protected byte[] transform(Object rawModule,
String internalTypeName,
Class> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] binaryRepresentation) {
JavaModule module = JavaModule.of(rawModule);
return transform(module,
module.getClassLoader(accessControlContext),
internalTypeName,
classBeingRedefined,
protectionDomain,
binaryRepresentation);
}
/**
* Applies a transformation for a class that was captured by this {@link ClassFileTransformer}.
*
* @param module The instrumented class's Java module in its wrapped form or {@code null} if the current VM does not support modules.
* @param classLoader The instrumented class's class loader.
* @param internalTypeName The internal name of the instrumented class.
* @param classBeingRedefined The loaded {@link Class} being redefined or {@code null} if no such class exists.
* @param protectionDomain The instrumented type's protection domain.
* @param binaryRepresentation The class file of the instrumented class in its current state.
* @return The transformed class file or an empty byte array if this transformer does not apply an instrumentation.
*/
private byte[] transform(JavaModule module,
ClassLoader classLoader,
String internalTypeName,
Class> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] binaryRepresentation) {
if (internalTypeName == null) {
return NO_TRANSFORMATION;
}
String binaryTypeName = internalTypeName.replace('/', '.');
try {
ClassFileLocator classFileLocator = ClassFileLocator.Simple.of(binaryTypeName,
binaryRepresentation,
ClassFileLocator.ForClassLoader.of(classLoader));
return transformation.resolve(descriptionStrategy.apply(binaryTypeName, classBeingRedefined, typeLocator, classLoader, classFileLocator),
classLoader,
module,
classBeingRedefined,
protectionDomain,
ignoredTypeMatcher).apply(initializationStrategy,
classFileLocator,
typeStrategy,
byteBuddy,
nativeMethodStrategy,
bootstrapInjectionStrategy,
accessControlContext,
listener);
} catch (Throwable throwable) {
listener.onError(binaryTypeName, classLoader, module, throwable);
return NO_TRANSFORMATION;
} finally {
listener.onComplete(binaryTypeName, classLoader, module);
}
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
ExecutingTransformer that = (ExecutingTransformer) other;
return byteBuddy.equals(that.byteBuddy)
&& typeLocator.equals(that.typeLocator)
&& typeStrategy.equals(that.typeStrategy)
&& initializationStrategy.equals(that.initializationStrategy)
&& listener.equals(that.listener)
&& nativeMethodStrategy.equals(that.nativeMethodStrategy)
&& bootstrapInjectionStrategy.equals(that.bootstrapInjectionStrategy)
&& descriptionStrategy == that.descriptionStrategy
&& accessControlContext.equals(that.accessControlContext)
&& ignoredTypeMatcher.equals(that.ignoredTypeMatcher)
&& transformation.equals(that.transformation);
}
@Override
public int hashCode() {
int result = byteBuddy.hashCode();
result = 31 * result + typeLocator.hashCode();
result = 31 * result + typeStrategy.hashCode();
result = 31 * result + initializationStrategy.hashCode();
result = 31 * result + listener.hashCode();
result = 31 * result + nativeMethodStrategy.hashCode();
result = 31 * result + bootstrapInjectionStrategy.hashCode();
result = 31 * result + descriptionStrategy.hashCode();
result = 31 * result + accessControlContext.hashCode();
result = 31 * result + ignoredTypeMatcher.hashCode();
result = 31 * result + transformation.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.Default.ExecutingTransformer{" +
"byteBuddy=" + byteBuddy +
", typeLocator=" + typeLocator +
", typeStrategy=" + typeStrategy +
", initializationStrategy=" + initializationStrategy +
", listener=" + listener +
", nativeMethodStrategy=" + nativeMethodStrategy +
", bootstrapInjectionStrategy=" + bootstrapInjectionStrategy +
", descriptionStrategy=" + descriptionStrategy +
", accessControlContext=" + accessControlContext +
", ignoredTypeMatcher=" + ignoredTypeMatcher +
", transformation=" + transformation +
'}';
}
/**
* A factory for creating a {@link ClassFileTransformer} for the current VM.
*/
protected interface Factory {
/**
* Creates a new class file transformer for the current VM.
*
* @param byteBuddy The Byte Buddy instance to be used.
* @param typeLocator The type locator to use.
* @param typeStrategy The definition handler to use.
* @param listener The listener to notify on transformations.
* @param nativeMethodStrategy The native method strategy to apply.
* @param accessControlContext The access control context to use for loading classes.
* @param initializationStrategy The initialization strategy to use for transformed types.
* @param bootstrapInjectionStrategy The injection strategy for injecting classes into the bootstrap class loader.
* @param descriptionStrategy The description strategy for resolving type descriptions for types.
* @param ignoredTypeMatcher Identifies types that should not be instrumented.
* @param transformation The transformation object for handling type transformations.
* @return A class file transformer for the current VM that supports the API of the current VM.
*/
ClassFileTransformer make(ByteBuddy byteBuddy,
TypeLocator typeLocator,
TypeStrategy typeStrategy,
Listener listener,
NativeMethodStrategy nativeMethodStrategy,
AccessControlContext accessControlContext,
InitializationStrategy initializationStrategy,
BootstrapInjectionStrategy bootstrapInjectionStrategy,
DescriptionStrategy descriptionStrategy,
RawMatcher ignoredTypeMatcher,
Transformation transformation);
/**
* A factory for a class file transformer on a JVM that supports the {@code java.lang.reflect.Module} API to override
* the newly added method of the {@link ClassFileTransformer} to capture an instrumented class's module.
*/
class ForJava9CapableVm implements Factory {
/**
* A constructor for creating a {@link ClassFileTransformer} that overrides the newly added method for extracting
* the {@code java.lang.reflect.Module} of an instrumented class.
*/
private final Constructor extends ClassFileTransformer> executingTransformer;
/**
* Creates a class file transformer factory for a Java 9 capable VM.
*
* @param executingTransformer A constructor for creating a {@link ClassFileTransformer} that overrides the newly added
* method for extracting the {@code java.lang.reflect.Module} of an instrumented class.
*/
protected ForJava9CapableVm(Constructor extends ClassFileTransformer> executingTransformer) {
this.executingTransformer = executingTransformer;
}
@Override
public ClassFileTransformer make(ByteBuddy byteBuddy,
TypeLocator typeLocator,
TypeStrategy typeStrategy,
Listener listener,
NativeMethodStrategy nativeMethodStrategy,
AccessControlContext accessControlContext,
InitializationStrategy initializationStrategy,
BootstrapInjectionStrategy bootstrapInjectionStrategy,
DescriptionStrategy descriptionStrategy,
RawMatcher ignoredTypeMatcher,
Transformation transformation) {
try {
return executingTransformer.newInstance(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
bootstrapInjectionStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Cannot access " + executingTransformer, exception);
} catch (InstantiationException exception) {
throw new IllegalStateException("Cannot instantiate " + executingTransformer.getDeclaringClass(), exception);
} catch (InvocationTargetException exception) {
throw new IllegalStateException("Cannot invoke " + executingTransformer, exception.getCause());
}
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
ForJava9CapableVm that = (ForJava9CapableVm) object;
return executingTransformer.equals(that.executingTransformer);
}
@Override
public int hashCode() {
return executingTransformer.hashCode();
}
@Override
public String toString() {
return "AgentBuilder.Default.ExecutingTransformer.Factory.ForJava9CapableVm{" +
"executingTransformer=" + executingTransformer +
'}';
}
}
/**
* A factory for a {@link ClassFileTransformer} on a VM that does not support the {@code java.lang.reflect.Module} API.
*/
enum ForLegacyVm implements Factory {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public ClassFileTransformer make(ByteBuddy byteBuddy,
TypeLocator typeLocator,
TypeStrategy typeStrategy,
Listener listener,
NativeMethodStrategy nativeMethodStrategy,
AccessControlContext accessControlContext,
InitializationStrategy initializationStrategy,
BootstrapInjectionStrategy bootstrapInjectionStrategy,
DescriptionStrategy descriptionStrategy,
RawMatcher ignoredTypeMatcher,
Transformation transformation) {
return new ExecutingTransformer(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
bootstrapInjectionStrategy,
descriptionStrategy,
ignoredTypeMatcher,
transformation);
}
@Override
public String toString() {
return "AgentBuilder.Default.ExecutingTransformer.Factory.ForLegacyVm." + name();
}
}
}
}
/**
* An abstract implementation of an agent builder that delegates all invocation to another instance.
*
* @param The type that is produced by chaining a matcher.
*/
protected abstract class Delegator> extends Matchable.AbstractBase implements AgentBuilder {
/**
* Materializes the currently described {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder}.
*
* @return An agent builder that represents the currently described entry of this instance.
*/
protected abstract AgentBuilder materialize();
@Override
public AgentBuilder with(ByteBuddy byteBuddy) {
return materialize().with(byteBuddy);
}
@Override
public AgentBuilder with(Listener listener) {
return materialize().with(listener);
}
@Override
public AgentBuilder with(TypeStrategy typeStrategy) {
return materialize().with(typeStrategy);
}
@Override
public AgentBuilder with(TypeLocator typeLocator) {
return materialize().with(typeLocator);
}
@Override
public AgentBuilder with(AccessControlContext accessControlContext) {
return materialize().with(accessControlContext);
}
@Override
public AgentBuilder with(InitializationStrategy initializationStrategy) {
return materialize().with(initializationStrategy);
}
@Override
public AgentBuilder with(RedefinitionStrategy redefinitionStrategy) {
return materialize().with(redefinitionStrategy);
}
@Override
public AgentBuilder with(LambdaInstrumentationStrategy lambdaInstrumentationStrategy) {
return materialize().with(lambdaInstrumentationStrategy);
}
@Override
public AgentBuilder with(DescriptionStrategy descriptionStrategy) {
return materialize().with(descriptionStrategy);
}
@Override
public AgentBuilder enableBootstrapInjection(Instrumentation instrumentation, File folder) {
return materialize().enableBootstrapInjection(instrumentation, folder);
}
@Override
public AgentBuilder disableBootstrapInjection() {
return materialize().disableBootstrapInjection();
}
@Override
public AgentBuilder enableNativeMethodPrefix(String prefix) {
return materialize().enableNativeMethodPrefix(prefix);
}
@Override
public AgentBuilder disableNativeMethodPrefix() {
return materialize().disableNativeMethodPrefix();
}
@Override
public AgentBuilder disableClassFormatChanges() {
return materialize().disableClassFormatChanges();
}
@Override
public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Class>... type) {
return materialize().assureReadEdgeTo(instrumentation, type);
}
@Override
public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, JavaModule... module) {
return materialize().assureReadEdgeTo(instrumentation, module);
}
@Override
public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Collection extends JavaModule> modules) {
return materialize().assureReadEdgeTo(instrumentation, modules);
}
@Override
public AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Class>... type) {
return materialize().assureReadEdgeFromAndTo(instrumentation, type);
}
@Override
public AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, JavaModule... module) {
return materialize().assureReadEdgeFromAndTo(instrumentation, module);
}
@Override
public AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Collection extends JavaModule> modules) {
return materialize().assureReadEdgeFromAndTo(instrumentation, modules);
}
@Override
public Identified.Narrowable type(ElementMatcher super TypeDescription> typeMatcher) {
return materialize().type(typeMatcher);
}
@Override
public Identified.Narrowable type(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher) {
return materialize().type(typeMatcher, classLoaderMatcher);
}
@Override
public Identified.Narrowable type(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher) {
return materialize().type(typeMatcher, classLoaderMatcher, moduleMatcher);
}
@Override
public Identified.Narrowable type(RawMatcher matcher) {
return materialize().type(matcher);
}
@Override
public Ignored ignore(ElementMatcher super TypeDescription> ignoredTypes) {
return materialize().ignore(ignoredTypes);
}
@Override
public Ignored ignore(ElementMatcher super TypeDescription> ignoredTypes, ElementMatcher super ClassLoader> ignoredClassLoaders) {
return materialize().ignore(ignoredTypes, ignoredClassLoaders);
}
@Override
public Ignored ignore(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher) {
return materialize().ignore(typeMatcher, classLoaderMatcher, moduleMatcher);
}
@Override
public Ignored ignore(RawMatcher rawMatcher) {
return materialize().ignore(rawMatcher);
}
@Override
public ClassFileTransformer makeRaw() {
return materialize().makeRaw();
}
@Override
public ClassFileTransformer installOn(Instrumentation instrumentation) {
return materialize().installOn(instrumentation);
}
@Override
public ClassFileTransformer installOnByteBuddyAgent() {
return materialize().installOnByteBuddyAgent();
}
}
/**
* A delegator transformer for further precising what types to ignore.
*/
protected class Ignoring extends Delegator implements Ignored {
/**
* A matcher for identifying types that should not be instrumented.
*/
private final RawMatcher rawMatcher;
/**
* Creates a new agent builder for further specifying what types to ignore.
*
* @param rawMatcher A matcher for identifying types that should not be instrumented.
*/
protected Ignoring(RawMatcher rawMatcher) {
this.rawMatcher = rawMatcher;
}
@Override
protected AgentBuilder materialize() {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
rawMatcher,
transformation);
}
@Override
public Ignored and(RawMatcher rawMatcher) {
return new Ignoring(new RawMatcher.Conjunction(this.rawMatcher, rawMatcher));
}
@Override
public Ignored or(RawMatcher rawMatcher) {
return new Ignoring(new RawMatcher.Disjunction(this.rawMatcher, rawMatcher));
}
/**
* Returns the outer instance.
*
* @return The outer instance.
*/
private Default getOuter() {
return Default.this;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& rawMatcher.equals(((Ignoring) other).rawMatcher)
&& Default.this.equals(((Ignoring) other).getOuter());
}
@Override
public int hashCode() {
int result = rawMatcher.hashCode();
result = 31 * result + Default.this.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.Default.Ignoring{" +
"rawMatcher=" + rawMatcher +
", agentBuilder=" + Default.this +
'}';
}
}
/**
* A helper class that describes a {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Default} after supplying
* a {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.RawMatcher} such that one or several
* {@link com.fitbur.mockito.bytebuddy.agent.builder.AgentBuilder.Transformer}s can be supplied.
*/
protected class Transforming extends Delegator implements Identified.Extendable, Identified.Narrowable {
/**
* The supplied raw matcher.
*/
private final RawMatcher rawMatcher;
/**
* The supplied transformer.
*/
private final Transformer transformer;
/**
* {@code true} if this transformer serves as a decorator.
*/
private final boolean decorator;
/**
* Creates a new matched default agent builder.
*
* @param rawMatcher The supplied raw matcher.
* @param transformer The supplied transformer.
* @param decorator {@code true} if this transformer serves as a decorator.
*/
protected Transforming(RawMatcher rawMatcher, Transformer transformer, boolean decorator) {
this.rawMatcher = rawMatcher;
this.transformer = transformer;
this.decorator = decorator;
}
@Override
protected AgentBuilder materialize() {
return new Default(byteBuddy,
typeLocator,
typeStrategy,
listener,
nativeMethodStrategy,
accessControlContext,
initializationStrategy,
redefinitionStrategy,
bootstrapInjectionStrategy,
lambdaInstrumentationStrategy,
descriptionStrategy,
ignoredTypeMatcher,
new Transformation.Compound(new Transformation.Simple(rawMatcher, transformer, decorator), transformation));
}
@Override
public Identified.Extendable transform(Transformer transformer) {
return new Transforming(rawMatcher, new Transformer.Compound(this.transformer, transformer), decorator);
}
@Override
public AgentBuilder asDecorator() {
return new Transforming(rawMatcher, transformer, true);
}
@Override
public Narrowable and(RawMatcher rawMatcher) {
return new Transforming(new RawMatcher.Conjunction(this.rawMatcher, rawMatcher), transformer, decorator);
}
@Override
public Narrowable or(RawMatcher rawMatcher) {
return new Transforming(new RawMatcher.Disjunction(this.rawMatcher, rawMatcher), transformer, decorator);
}
/**
* Returns the outer instance.
*
* @return The outer instance.
*/
private Default getOuter() {
return Default.this;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& decorator == ((Transforming) other).decorator
&& rawMatcher.equals(((Transforming) other).rawMatcher)
&& transformer.equals(((Transforming) other).transformer)
&& Default.this.equals(((Transforming) other).getOuter());
}
@Override
public int hashCode() {
int result = rawMatcher.hashCode();
result = 31 * result + (decorator ? 1 : 0);
result = 31 * result + transformer.hashCode();
result = 31 * result + Default.this.hashCode();
return result;
}
@Override
public String toString() {
return "AgentBuilder.Default.Transforming{" +
"rawMatcher=" + rawMatcher +
", transformer=" + transformer +
", decorator=" + decorator +
", agentBuilder=" + Default.this +
'}';
}
}
}
}