net.bytebuddy.agent.builder.AgentBuilder Maven / Gradle / Ivy
/*
* Copyright 2014 - Present Rafael Winterhalter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bytebuddy.agent.builder;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.NamingStrategy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.build.AccessControllerPlugin;
import net.bytebuddy.build.EntryPoint;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.build.Plugin;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.modifier.FieldManifestation;
import net.bytebuddy.description.modifier.MethodManifestation;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.TypeManifestation;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.PackageDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.NexusAccessor;
import net.bytebuddy.dynamic.TypeResolutionStrategy;
import net.bytebuddy.dynamic.VisibilityBridgeStrategy;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.ExceptionMethod;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.LoadedTypeInitializer;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.Duplication;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.TypeCreation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
import net.bytebuddy.implementation.bytecode.constant.TextConstant;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.matcher.LatentMatcher;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.CompoundList;
import net.bytebuddy.utility.JavaConstant;
import net.bytebuddy.utility.JavaModule;
import net.bytebuddy.utility.JavaType;
import net.bytebuddy.utility.dispatcher.JavaDispatcher;
import net.bytebuddy.utility.nullability.AlwaysNull;
import net.bytebuddy.utility.nullability.MaybeNull;
import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.io.File;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static net.bytebuddy.matcher.ElementMatchers.any;
import static net.bytebuddy.matcher.ElementMatchers.hasMethodName;
import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.isExtensionClassLoader;
import static net.bytebuddy.matcher.ElementMatchers.isSynthetic;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.supportsModules;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
/**
*
* 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 net.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.
*
*
* Note: Any transformation is performed using the {@code java.security.AccessControlContext} of an agent's creator.
*
*
* Important: Types that implement lambda expressions (functional interfaces) are not instrumented by default but
* only when enabling the builder's {@link LambdaInstrumentationStrategy}.
*
*/
@SuppressWarnings("overloads")
public interface AgentBuilder {
/**
* Defines the given {@link net.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 net.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 a circularity lock that is acquired upon executing code that potentially loads new classes. While the
* lock is acquired, any class file transformer refrains from transforming any classes. By default, all created
* agents use a shared {@link CircularityLock} to avoid that any classes that are required to execute an agent
* causes a {@link ClassCircularityError}.
*
* @param circularityLock The circularity lock to use.
* @return A new instance of this agent builder which creates an agent that uses the supplied circularity lock.
*/
AgentBuilder with(CircularityLock circularityLock);
/**
* Defines the use of the given type locator for locating a {@link TypeDescription} for an instrumented type.
*
* @param poolStrategy 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(PoolStrategy poolStrategy);
/**
* Defines the use of the given location strategy for locating binary data to given class names.
*
* @param locationStrategy The location strategy to use.
* @return A new instance of this agent builder which uses the given location strategy for looking up class files.
*/
AgentBuilder with(LocationStrategy locationStrategy);
/**
* Registers an additional class file locator for types that are globally available but cannot be located
* otherwise. Typically, those types are injected classes into the boot loader.
*
* @param classFileLocator The class file locator to add.
* @return A new instance of this agent builder which uses the given class file locator for global type lookup.
*/
AgentBuilder with(ClassFileLocator classFileLocator);
/**
* 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);
/**
* 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.
*
*
* Note: Defining a redefinition strategy resets any refinements of a previously set redefinition strategy.
*
*
* Important: Most JVMs do not support changes of a class's structure after a class was already
* loaded. Therefore, it is typically required that this class file transformer was built while enabling
* {@link AgentBuilder#disableClassFormatChanges()}.
*
*
* @param redefinitionStrategy The redefinition strategy to apply.
* @return A new instance of this agent builder that applies the given redefinition strategy.
*/
RedefinitionListenable.WithoutBatchStrategy 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);
/**
* Specifies a fallback strategy to that this agent builder applies upon installing an agent and during class file transformation.
*
* @param fallbackStrategy The fallback strategy to be used.
* @return A new agent builder that applies the supplied fallback strategy.
*/
AgentBuilder with(FallbackStrategy fallbackStrategy);
/**
* Specifies a class file buffer strategy that determines the use of the buffer supplied to a class file transformer.
*
* @param classFileBufferStrategy The class file buffer strategy to use.
* @return A new agent builder that applies the supplied class file buffer strategy.
*/
AgentBuilder with(ClassFileBufferStrategy classFileBufferStrategy);
/**
* Adds an installation listener that is notified during installation events. Installation listeners are only invoked if
* a class file transformer is installed using this agent builder's installation methods and uninstalled via the created
* {@link ResettableClassFileTransformer}'s {@code reset} methods.
*
* @param installationListener The installation listener to register.
* @return A new agent builder that applies the supplied installation listener.
*/
AgentBuilder with(InstallationListener installationListener);
/**
* Defines a strategy for injecting auxiliary types into the target class loader.
*
* @param injectionStrategy The injection strategy to use.
* @return A new agent builder with the supplied injection strategy configured.
*/
AgentBuilder with(InjectionStrategy injectionStrategy);
/**
* Adds a decorator for the created class file transformer.
*
* @param transformerDecorator A decorator to wrap the created class file transformer.
* @return A new agent builder that applies the supplied transformer decorator.
*/
AgentBuilder with(TransformerDecorator transformerDecorator);
/**
* 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 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_FROZEN}
* (unless it is configured as {@link TypeStrategy.Default#DECORATE} where this strategy is retained)
* as well as configuring the underlying {@link ByteBuddy} instance to use a {@link net.bytebuddy.implementation.Implementation.Context.Disabled}.
* Using this strategy also configures Byte Buddy to create frozen instrumented types and discards any explicit configuration.
*
*
* @return A new instance of this agent builder that does not apply any implicit changes to the received class file.
*/
AgentBuilder disableClassFormatChanges();
/**
*
* Warms up the generated {@link ClassFileTransformer} to trigger class loading of classes used by the transformer
* prior to its actual use. Ideally, warmup should include classes that cause a transformation and classes that
* are ignored. Warming up can be especially useful when transforming classes on the boot path, where circularity
* errors are more likely. At the same time, warming up might load classes that are expected to be unloaded
* when this agent is installed.
*
*
* Important: Warming up is applied just as a regular transformation and will also invoke the {@link Listener}.
* This is done to avoid that listener classes can cause circularities. It is the users responsibility to suppress
* such log output, if necessary.
*
*
* @param type The types to include in the warmup.
* @return A new agent builder that considers the supplied classes in its warmup.
*/
AgentBuilder warmUp(Class>... type);
/**
*
* Warms up the generated {@link ClassFileTransformer} to trigger class loading of classes used by the transformer
* prior to its actual use. Ideally, warmup should include classes that cause a transformation and classes that
* are ignored. Warming up can be especially useful when transforming classes on the boot path, where circularity
* errors are more likely. At the same time, warming up might load classes that are expected to be unloaded
* when this agent is installed.
*
*
* Important: Warming up is applied just as a regular transformation and will also invoke the {@link Listener}.
* This is done to avoid that listener classes can cause circularities. It is the users responsibility to suppress
* such log output, if necessary.
*
*
* @param types The types to include in the warmup.
* @return A new agent builder that considers the supplied classes in its warmup.
*/
AgentBuilder warmUp(Collection> types);
/**
* 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.
* Setting this option will also ensure that the instrumented type's package is opened to the target module, if applicable.
*
* @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.
* Setting this option will also ensure that the instrumented type's package is opened to the target module.
*
* @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.
* Setting this option will also ensure that the instrumented type's package is opened to the target module.
*
* @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 net.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, any matcher is executed in registration order with matchers that were registered
* first being executed first. Doing so, later transformations can override transformations that are applied by this matcher. To avoid this, it is
* possible to register this transformation as terminal via {@link Identified.Extendable#asTerminalTransformation()} where no subsequent matchers
* are applied if this matcher matched a given type.
*
*
* 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 net.bytebuddy.matcher.ElementMatcher} that is applied on the type being loaded that
* decides if the entailed {@link net.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 net.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 net.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, any matcher is executed in registration order with matchers that were registered
* first being executed first. Doing so, later transformations can override transformations that are applied by this matcher. To avoid this, it is
* possible to register this transformation as terminal via {@link Identified.Extendable#asTerminalTransformation()} where no subsequent matchers
* are applied if this matcher matched a given type.
*
*
* 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 net.bytebuddy.matcher.ElementMatcher} that is applied on the type being
* loaded that decides if the entailed
* {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s should be applied for
* that type.
* @param classLoaderMatcher An {@link net.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 net.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 net.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, any matcher is executed in registration order with matchers that were registered
* first being executed first. Doing so, later transformations can override transformations that are applied by this matcher. To avoid this, it is
* possible to register this transformation as terminal via {@link Identified.Extendable#asTerminalTransformation()} where no subsequent matchers
* are applied if this matcher matched a given type.
*
*
* 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 net.bytebuddy.matcher.ElementMatcher} that is applied on the type being
* loaded that decides if the entailed
* {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s should be applied for
* that type.
* @param classLoaderMatcher An {@link net.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 net.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 net.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 net.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, any matcher is executed in registration order with matchers that were registered
* first being executed first. Doing so, later transformations can override transformations that are applied by this matcher. To avoid this, it is
* possible to register this transformation as terminal via {@link Identified.Extendable#asTerminalTransformation()} where no subsequent matchers
* are applied if this matcher matched a given type.
*
*
* 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 net.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 net.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 net.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 net.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 net.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 ResettableClassFileTransformer} that implements the configuration of this
* agent builder. When using a raw class file transformer, the {@link InstallationListener} callbacks are
* not invoked and the set {@link RedefinitionStrategy} is not applied onto currently loaded classes.
*
* @return A class file transformer that implements the configuration of this agent builder.
*/
ClassFileTransformer makeRaw();
/**
*
* Creates and installs a {@link ResettableClassFileTransformer} 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.
*
*
* In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied
* via the {@link ResettableClassFileTransformer}'s {@code reset} methods.
*
*
* @param instrumentation The instrumentation on which this agent builder's configuration is to be installed.
* @return The installed class file transformer.
*/
ResettableClassFileTransformer installOn(Instrumentation instrumentation);
/**
*
* Creates and installs a {@link ResettableClassFileTransformer} that implements the configuration of
* this agent builder with the Byte Buddy-agent which must be installed prior to calling this method. If retransformation
* is enabled, the installation also causes all loaded types to be retransformed.
*
*
* In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied
* via the {@link ResettableClassFileTransformer}'s {@code reset} methods.
*
*
* @return The installed class file transformer.
* @see AgentBuilder#installOn(Instrumentation)
*/
ResettableClassFileTransformer installOnByteBuddyAgent();
/**
*
* Creates and installs a {@link ResettableClassFileTransformer} 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 which have changed compared to the previous
* class file transformer that is provided as an argument. Without specification, {@link PatchMode#OVERLAP} is used.
*
*
* In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied
* via the {@link ResettableClassFileTransformer}'s {@code reset} methods.
*
*
* @param instrumentation The instrumentation on which this agent builder's configuration is to be installed.
* @param classFileTransformer The class file transformer that is being patched.
* @return The installed class file transformer.
*/
ResettableClassFileTransformer patchOn(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer);
/**
*
* Creates and installs a {@link ResettableClassFileTransformer} 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 which have changed compared to the previous
* class file transformer that is provided as an argument. Without specification, {@link PatchMode#OVERLAP} is used.
*
*
* In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied
* via the {@link ResettableClassFileTransformer}'s {@code reset} methods.
*
*
* @param instrumentation The instrumentation on which this agent builder's configuration is to be installed.
* @param classFileTransformer The class file transformer that is being patched.
* @param differentialMatcher The differential matcher to decide what types need retransformation.
* @return The installed class file transformer.
*/
ResettableClassFileTransformer patchOn(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, RawMatcher differentialMatcher);
/**
*
* Creates and installs a {@link ResettableClassFileTransformer} 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 which have changed compared to the previous
* class file transformer that is provided as an argument.
*
*
* In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied
* via the {@link ResettableClassFileTransformer}'s {@code reset} methods.
*
*
* @param instrumentation The instrumentation on which this agent builder's configuration is to be installed.
* @param classFileTransformer The class file transformer that is being patched.
* @param patchMode The patch mode to apply.
* @return The installed class file transformer.
*/
ResettableClassFileTransformer patchOn(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, PatchMode patchMode);
/**
*
* Creates and installs a {@link ResettableClassFileTransformer} 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 which have changed compared to the previous
* class file transformer that is provided as an argument.
*
*
* In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied
* via the {@link ResettableClassFileTransformer}'s {@code reset} methods.
*
*
* @param instrumentation The instrumentation on which this agent builder's configuration is to be installed.
* @param classFileTransformer The class file transformer that is being patched.
* @param differentialMatcher The differential matcher to decide what types need retransformation.
* @param patchMode The patch mode to apply.
* @return The installed class file transformer.
*/
ResettableClassFileTransformer patchOn(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, RawMatcher differentialMatcher, PatchMode patchMode);
/**
*
* Creates and installs a {@link ResettableClassFileTransformer} that implements the configuration of
* this agent builder with the Byte Buddy-agent which must be installed prior to calling this method. If retransformation
* is enabled, the installation also causes all loaded types to be retransformed which have changed compared to the previous
* class file transformer that is provided as an argument. Without specification, {@link PatchMode#OVERLAP} is used.
*
*
* In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied
* via the {@link ResettableClassFileTransformer}'s {@code reset} methods.
*
*
* @param classFileTransformer The class file transformer that is being patched.
* @return The installed class file transformer.
* @see AgentBuilder#patchOn(Instrumentation, ResettableClassFileTransformer)
*/
ResettableClassFileTransformer patchOnByteBuddyAgent(ResettableClassFileTransformer classFileTransformer);
/**
*
* Creates and installs a {@link ResettableClassFileTransformer} that implements the configuration of
* this agent builder with the Byte Buddy-agent which must be installed prior to calling this method. If retransformation
* is enabled, the installation also causes all loaded types to be retransformed which have changed compared to the previous
* class file transformer that is provided as an argument.
*
*
* In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied
* via the {@link ResettableClassFileTransformer}'s {@code reset} methods.
*
*
* @param classFileTransformer The class file transformer that is being patched.
* @param patchMode The patch mode to apply.
* @return The installed class file transformer.
* @see AgentBuilder#patchOn(Instrumentation, ResettableClassFileTransformer, PatchMode)
*/
ResettableClassFileTransformer patchOnByteBuddyAgent(ResettableClassFileTransformer classFileTransformer, PatchMode patchMode);
/**
* 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);
}
/**
* Allows to further specify ignored types.
*/
interface Ignored extends Matchable, AgentBuilder {
/* this is merely a unionizing interface that does not declare methods */
}
/**
* An agent builder configuration that allows the registration of listeners to the redefinition process.
*/
interface RedefinitionListenable extends AgentBuilder {
/**
*
* A redefinition listener is invoked before each batch of type redefinitions and on every error as well as
* after the redefinition was completed. A redefinition listener can be used for debugging or logging purposes
* and to apply actions between each batch, e.g. to pause or wait in order to avoid rendering the current VM
* non-responsive if a lot of classes are redefined.
*
*
* Adding several listeners does not replace previous listeners but applies them in the registration order.
*
*
* @param redefinitionListener The listener to register.
* @return A new instance of this agent builder which notifies the specified listener upon type redefinitions.
*/
RedefinitionListenable with(RedefinitionStrategy.Listener redefinitionListener);
/**
* Specifies resubmission for given unloaded types or types that fail upon an exception during instrumentation.
*
* @param resubmissionScheduler The resubmission scheduler to use.
* @return A new builder to determine what types should be resubmitted given the supplied resubmission scheduler.
*/
WithoutResubmissionSpecification withResubmission(RedefinitionStrategy.ResubmissionScheduler resubmissionScheduler);
/**
* A matcher that determines if types should be resubmitted if it is not yet loaded and if an exception is raised.
*/
interface ResubmissionOnErrorMatcher {
/**
* Returns {@code true} if a type should be resubmitted if it is not yet loaded and an exception occurs during instrumentation.
*
* @param throwable The exception being raised.
* @param typeName The name of the instrumented type.
* @param classLoader The class loader of the instrumented type or {@code null} if the type is loaded by the bootstrap class loader.
* @param module The module of the instrumented type or {@code null} if the current VM does not support modules.
* @return {@code true} if the type should be resubmitted.
*/
boolean matches(Throwable throwable, String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module);
/**
* A trivial matcher for resubmission upon an exception.
*/
enum Trivial implements ResubmissionOnErrorMatcher {
/**
* Always matches a type.
*/
MATCHING(true),
/**
* Never matches a type.
*/
NON_MATCHING(false);
/**
* {@code true} if this matcher is matching.
*/
private final boolean matching;
/**
* Creates a new trivial matcher for a resubmission upon an exception.
*
* @param matching {@code true} if this matcher is matching.
*/
Trivial(boolean matching) {
this.matching = matching;
}
/**
* {@inheritDoc}
*/
public boolean matches(Throwable throwable, String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
return matching;
}
}
/**
* A matcher for resubmission upon an error that matches both of the supplied delegate matchers.
*/
@HashCodeAndEqualsPlugin.Enhance
class Conjunction implements ResubmissionOnErrorMatcher {
/**
* The represented matchers in their application order.
*/
private final List matchers;
/**
* Creates a new conjunction for a resubmission matcher upon an error.
*
* @param matcher The represented matchers in their application order.
*/
public Conjunction(ResubmissionOnErrorMatcher... matcher) {
this(Arrays.asList(matcher));
}
/**
* Creates a new conjunction for a resubmission matcher upon an error.
*
* @param matchers The represented matchers in their application order.
*/
public Conjunction(List extends ResubmissionOnErrorMatcher> matchers) {
this.matchers = new ArrayList(matchers.size());
for (ResubmissionOnErrorMatcher matcher : matchers) {
if (matcher instanceof Conjunction) {
this.matchers.addAll(((Conjunction) matcher).matchers);
} else if (matcher != Trivial.MATCHING) {
this.matchers.add(matcher);
}
}
}
/**
* {@inheritDoc}
*/
public boolean matches(Throwable throwable, String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
for (ResubmissionOnErrorMatcher matcher : matchers) {
if (!matcher.matches(throwable, typeName, classLoader, module)) {
return false;
}
}
return true;
}
}
/**
* A matcher for resubmission upon an error that matches either of the supplied delegate matchers.
*/
@HashCodeAndEqualsPlugin.Enhance
class Disjunction implements ResubmissionOnErrorMatcher {
/**
* The represented matchers in their application order.
*/
private final List matchers;
/**
* Creates a new disjunction for a resubmission matcher upon an error.
*
* @param matcher The represented matchers in their application order.
*/
public Disjunction(ResubmissionOnErrorMatcher... matcher) {
this(Arrays.asList(matcher));
}
/**
* Creates a new conjunction for a resubmission matcher upon an error.
*
* @param matchers The represented matchers in their application order.
*/
public Disjunction(List extends ResubmissionOnErrorMatcher> matchers) {
this.matchers = new ArrayList(matchers.size());
for (ResubmissionOnErrorMatcher matcher : matchers) {
if (matcher instanceof Disjunction) {
this.matchers.addAll(((Disjunction) matcher).matchers);
} else if (matcher != Trivial.NON_MATCHING) {
this.matchers.add(matcher);
}
}
}
/**
* {@inheritDoc}
*/
public boolean matches(Throwable throwable, String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
for (ResubmissionOnErrorMatcher matcher : matchers) {
if (matcher.matches(throwable, typeName, classLoader, module)) {
return true;
}
}
return false;
}
}
/**
* A matcher for resubmission upon error that uses element matchers for each argument to determine a resubmission.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForElementMatchers implements ResubmissionOnErrorMatcher {
/**
* The matcher to use for the exception that was caused.
*/
private final ElementMatcher super Throwable> exceptionMatcher;
/**
* The matcher to use for the instrumented type's name.
*/
private final ElementMatcher typeNameMatcher;
/**
* The matcher to use for the instrumented type's class loader.
*/
private final ElementMatcher super ClassLoader> classLoaderMatcher;
/**
* The matcher to use for the instrumented type's module.
*/
private final ElementMatcher super JavaModule> moduleMatcher;
/**
* Creates a new matcher for resubmission upon an exception that is using element matchers.
*
* @param exceptionMatcher The matcher to use for the exception that was caused.
* @param typeNameMatcher The matcher to use for the instrumented type's name.
* @param classLoaderMatcher The matcher to use for the instrumented type's class loader.
* @param moduleMatcher The matcher to use for the instrumented type's module.
*/
public ForElementMatchers(ElementMatcher super Throwable> exceptionMatcher,
ElementMatcher typeNameMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher) {
this.exceptionMatcher = exceptionMatcher;
this.typeNameMatcher = typeNameMatcher;
this.classLoaderMatcher = classLoaderMatcher;
this.moduleMatcher = moduleMatcher;
}
/**
* {@inheritDoc}
*/
public boolean matches(Throwable throwable, String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
return exceptionMatcher.matches(throwable)
&& typeNameMatcher.matches(typeName)
&& classLoaderMatcher.matches(classLoader)
&& moduleMatcher.matches(module);
}
}
}
/**
* A matcher that determines if types should be resubmitted if it is not yet loaded.
*/
interface ResubmissionImmediateMatcher {
/**
* Returns {@code true} if a type should be resubmitted if it is not yet loaded.
*
* @param typeName The name of the instrumented type.
* @param classLoader The class loader of the instrumented type or {@code null} if the type is loaded by the bootstrap class loader.
* @param module The module of the instrumented type or {@code null} if the current VM does not support modules.
* @return {@code true} if the type should be resubmitted.
*/
boolean matches(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module);
/**
* A trivial matcher for immediate resubmission.
*/
enum Trivial implements ResubmissionImmediateMatcher {
/**
* Always matches a type.
*/
MATCHING(true),
/**
* Never matches a type.
*/
NON_MATCHING(false);
/**
* {@code true} if this matcher is matching.
*/
private final boolean matching;
/**
* Creates a new trivial matcher for immediate resubmission.
*
* @param matching {@code true} if this matcher is matching.
*/
Trivial(boolean matching) {
this.matching = matching;
}
/**
* {@inheritDoc}
*/
public boolean matches(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
return matching;
}
}
/**
* A matcher for immediate resubmission that matches both of the supplied delegate matchers.
*/
@HashCodeAndEqualsPlugin.Enhance
class Conjunction implements ResubmissionImmediateMatcher {
/**
* The matchers in their application order.
*/
private final List matchers;
/**
* Creates a new conjunction for an immediate resubmission matcher.
*
* @param matcher The matchers in their application order.
*/
public Conjunction(ResubmissionImmediateMatcher... matcher) {
this(Arrays.asList(matcher));
}
/**
* Creates a new conjunction for an immediate resubmission matcher.
*
* @param matchers The matchers in their application order.
*/
public Conjunction(List extends ResubmissionImmediateMatcher> matchers) {
this.matchers = new ArrayList(matchers.size());
for (ResubmissionImmediateMatcher matcher : matchers) {
if (matcher instanceof Conjunction) {
this.matchers.addAll(((Conjunction) matcher).matchers);
} else if (matcher != Trivial.NON_MATCHING) {
this.matchers.add(matcher);
}
}
}
/**
* {@inheritDoc}
*/
public boolean matches(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
for (ResubmissionImmediateMatcher matcher : matchers) {
if (!matcher.matches(typeName, classLoader, module)) {
return false;
}
}
return true;
}
}
/**
* A matcher for immediate resubmission that matches either of the supplied delegate matchers.
*/
@HashCodeAndEqualsPlugin.Enhance
class Disjunction implements ResubmissionImmediateMatcher {
/**
* The matchers in their application order.
*/
private final List matchers;
/**
* Creates a new conjunction for an immediate resubmission matcher.
*
* @param matcher The matchers in their application order.
*/
public Disjunction(ResubmissionImmediateMatcher... matcher) {
this(Arrays.asList(matcher));
}
/**
* Creates a new disjunction for an immediate resubmission matcher.
*
* @param matchers The matchers in their application order.
*/
public Disjunction(List extends ResubmissionImmediateMatcher> matchers) {
this.matchers = new ArrayList(matchers.size());
for (ResubmissionImmediateMatcher matcher : matchers) {
if (matcher instanceof Disjunction) {
this.matchers.addAll(((Disjunction) matcher).matchers);
} else if (matcher != Trivial.NON_MATCHING) {
this.matchers.add(matcher);
}
}
}
/**
* {@inheritDoc}
*/
public boolean matches(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
for (ResubmissionImmediateMatcher matcher : matchers) {
if (matcher.matches(typeName, classLoader, module)) {
return true;
}
}
return false;
}
}
/**
* A matcher for immediate resubmission that uses element matchers for each argument to determine a resubmission.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForElementMatchers implements ResubmissionImmediateMatcher {
/**
* The matcher to use for the instrumented type's name.
*/
private final ElementMatcher typeNameMatcher;
/**
* The matcher to use for the instrumented type's class loader.
*/
private final ElementMatcher super ClassLoader> classLoaderMatcher;
/**
* The matcher to use for the instrumented type's module.
*/
private final ElementMatcher super JavaModule> moduleMatcher;
/**
* Creates a new matcher for immediate resubmission that is using element matchers.
*
* @param typeNameMatcher The matcher to use for the instrumented type's name.
* @param classLoaderMatcher The matcher to use for the instrumented type's class loader.
* @param moduleMatcher The matcher to use for the instrumented type's module.
*/
public ForElementMatchers(ElementMatcher typeNameMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher) {
this.typeNameMatcher = typeNameMatcher;
this.classLoaderMatcher = classLoaderMatcher;
this.moduleMatcher = moduleMatcher;
}
/**
* {@inheritDoc}
*/
public boolean matches(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
return typeNameMatcher.matches(typeName)
&& classLoaderMatcher.matches(classLoader)
&& moduleMatcher.matches(module);
}
}
}
/**
* An {@link AgentBuilder} specification that requires a resubmission specification.
*/
interface WithoutResubmissionSpecification {
/**
* Specifies that transformations of unloaded types that yield an error are resubmitted as transformation of the
* loaded type.
*
* @return A new agent builder that allows for further resubmission specifications.
*/
WithResubmissionSpecification resubmitOnError();
/**
* Specifies that transformations of unloaded types that yield an error are resubmitted as transformation of the
* loaded type, given that the specified matcher matches the type in question.
*
* @param exceptionMatcher Determines if a type should be resubmitted upon a given exception.
* @return A new agent builder that allows for further resubmission specifications.
*/
WithResubmissionSpecification resubmitOnError(ElementMatcher super Throwable> exceptionMatcher);
/**
* Specifies that transformations of unloaded types that yield an error are resubmitted as transformation of the
* loaded type, given that the specified matchers match the type in question.
*
* @param exceptionMatcher Determines if a type should be resubmitted upon a given exception.
* @param typeNameMatcher Determines if a type should be resubmitted if the type has a given name.
* @return A new agent builder that allows for further resubmission specifications.
*/
WithResubmissionSpecification resubmitOnError(ElementMatcher super Throwable> exceptionMatcher,
ElementMatcher typeNameMatcher);
/**
* Specifies that transformations of unloaded types that yield an error are resubmitted as transformation of the
* loaded type, given that the specified matchers match the type in question.
*
* @param exceptionMatcher Determines if a type should be resubmitted upon a given exception.
* @param typeNameMatcher Determines if a type should be resubmitted if the type has a given name.
* @param classLoaderMatcher Determines if a type should be resubmitted upon being loaded by a given class loader.
* @return A new agent builder that allows for further resubmission specifications.
*/
WithResubmissionSpecification resubmitOnError(ElementMatcher super Throwable> exceptionMatcher,
ElementMatcher typeNameMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher);
/**
* Specifies that transformations of unloaded types that yield an error are resubmitted as transformation of the
* loaded type, given that the specified matchers match the type in question.
*
* @param exceptionMatcher Determines if a type should be resubmitted upon a given exception.
* @param typeNameMatcher Determines if a type should be resubmitted if the type has a given name.
* @param classLoaderMatcher Determines if a type should be resubmitted upon being loaded by a given class loader.
* @param moduleMatcher Determines if a type should be resubmitted upon a given Java module.
* @return A new agent builder that allows for further resubmission specifications.
*/
WithResubmissionSpecification resubmitOnError(ElementMatcher super Throwable> exceptionMatcher,
ElementMatcher typeNameMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher);
/**
* Specifies that transformations of unloaded types that yield an error are resubmitted as transformation of the
* loaded type, given that the specified matcher matches the type in question.
*
* @param matcher Determines if a type should be resubmitted.
* @return A new agent builder that allows for further resubmission specifications.
*/
WithResubmissionSpecification resubmitOnError(ResubmissionOnErrorMatcher matcher);
/**
* Specifies that transformations of unloaded types should not be transformed when they are loaded for the first
* time but should rather be resubmitted after they are loaded.
*
* @return A new agent builder that allows for further resubmission specifications.
*/
WithResubmissionSpecification resubmitImmediate();
/**
* Specifies that transformations of unloaded types should not be transformed when they are loaded for the first
* time but should rather be resubmitted after they are loaded.
*
* @param typeNameMatcher Determines if a type should be resubmitted if the type has a given name.
* @return A new agent builder that allows for further resubmission specifications.
*/
WithResubmissionSpecification resubmitImmediate(ElementMatcher typeNameMatcher);
/**
* Specifies that transformations of unloaded types should not be transformed when they are loaded for the first
* time but should rather be resubmitted after they are loaded.
*
* @param typeNameMatcher Determines if a type should be resubmitted if the type has a given name.
* @param classLoaderMatcher Determines if a type should be resubmitted upon being loaded by a given class loader.
* @return A new agent builder that allows for further resubmission specifications.
*/
WithResubmissionSpecification resubmitImmediate(ElementMatcher typeNameMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher);
/**
* Specifies that transformations of unloaded types should not be transformed when they are loaded for the first
* time but should rather be resubmitted after they are loaded.
*
* @param typeNameMatcher Determines if a type should be resubmitted if the type has a given name.
* @param classLoaderMatcher Determines if a type should be resubmitted upon being loaded by a given class loader.
* @param moduleMatcher Determines if a type should be resubmitted upon a given Java module.
* @return A new agent builder that allows for further resubmission specifications.
*/
WithResubmissionSpecification resubmitImmediate(ElementMatcher typeNameMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher);
/**
* Specifies that transformations of unloaded types should not be transformed when they are loaded for the first
* time but should rather be resubmitted after they are loaded.
*
* @param matcher Determines if a type should be resubmitted.
* @return A new agent builder that allows for further resubmission specifications.
*/
WithResubmissionSpecification resubmitImmediate(ResubmissionImmediateMatcher matcher);
}
/**
* A complete but extendable resubmission specification.
*/
interface WithResubmissionSpecification extends WithoutResubmissionSpecification, AgentBuilder {
/* union type */
}
/**
* An agent builder configuration strategy that allows the definition of a discovery strategy.
*/
interface WithImplicitDiscoveryStrategy extends RedefinitionListenable {
/**
* Limits the redefinition attempt to the specified types.
*
* @param type The types to consider for redefinition.
* @return A new instance of this agent builder which only considers the supplied types for redefinition.
*/
RedefinitionListenable redefineOnly(Class>... type);
/**
* A discovery strategy is responsible for locating loaded types that should be considered for redefinition.
*
* @param redefinitionDiscoveryStrategy The redefinition discovery strategy to use.
* @return A new instance of this agent builder which makes use of the specified discovery strategy.
*/
RedefinitionListenable with(RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy);
}
/**
* An agent builder configuration that allows the configuration of a batching strategy.
*/
interface WithoutBatchStrategy extends WithImplicitDiscoveryStrategy {
/**
* A batch allocator is responsible for diving a redefining of existing types into several chunks. This allows
* to narrow down errors for the redefining of specific types or to apply a {@link RedefinitionStrategy.Listener}
* action between chunks.
*
* @param redefinitionBatchAllocator The batch allocator to use.
* @return A new instance of this agent builder which makes use of the specified batch allocator.
*/
WithImplicitDiscoveryStrategy with(RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator);
}
}
/**
* Describes an {@link net.bytebuddy.agent.builder.AgentBuilder} which was handed a matcher for identifying
* types to instrumented in order to supply one or several
* {@link net.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 net.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 previously defined transformation as terminal such that no subsequent transformers are applied even
* if their matchers would include the type that was matched for applying this transformer. If this option is not set,
* subsequent transformations are applied after this transformation such that it is possible that they override non-additive
* type transformations.
*
* @return A new agent builder that applies the previously configured transformer terminally.
*/
AgentBuilder asTerminalTransformation();
}
}
/**
* A matcher that allows to determine if a {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}
* should be applied during the execution of a {@link java.lang.instrument.ClassFileTransformer} that was
* generated by an {@link net.bytebuddy.agent.builder.AgentBuilder}.
*/
interface RawMatcher {
/**
* Decides if the given {@code typeDescription} should be instrumented with the entailed
* {@link net.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 or {@code null} if none is available.
* @return {@code true} if the entailed {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s should
* be applied for the given {@code typeDescription}.
*/
boolean matches(TypeDescription typeDescription,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull Class> classBeingRedefined,
@MaybeNull ProtectionDomain protectionDomain);
/**
* A matcher that always or never matches a type.
*/
enum Trivial implements RawMatcher {
/**
* Always matches a type.
*/
MATCHING(true),
/**
* Never matches a type.
*/
NON_MATCHING(false);
/**
* {@code true} if this matcher always matches a type.
*/
private final boolean matches;
/**
* Creates a new trivial raw matcher.
*
* @param matches {@code true} if this matcher always matches a type.
*/
Trivial(boolean matches) {
this.matches = matches;
}
/**
* {@inheritDoc}
*/
public boolean matches(TypeDescription typeDescription,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull Class> classBeingRedefined,
@MaybeNull ProtectionDomain protectionDomain) {
return matches;
}
}
/**
* A raw matcher indicating the state of a type's class loading.
*/
enum ForLoadState implements RawMatcher {
/**
* Indicates that a type was already loaded.
*/
LOADED(false),
/**
* Indicates that a type was not yet loaded.
*/
UNLOADED(true);
/**
* {@code true} if a type is expected to be unloaded..
*/
private final boolean unloaded;
/**
* Creates a new load state matcher.
*
* @param unloaded {@code true} if a type is expected to be unloaded..
*/
ForLoadState(boolean unloaded) {
this.unloaded = unloaded;
}
/**
* {@inheritDoc}
*/
public boolean matches(TypeDescription typeDescription,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull Class> classBeingRedefined,
@MaybeNull ProtectionDomain protectionDomain) {
return classBeingRedefined == null == unloaded;
}
}
/**
* Only matches loaded types that can be fully resolved. Types with missing dependencies might not be
* resolvable and can therefore trigger errors during redefinition.
*/
enum ForResolvableTypes implements RawMatcher {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public boolean matches(TypeDescription typeDescription,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull Class> classBeingRedefined,
@MaybeNull ProtectionDomain protectionDomain) {
if (classBeingRedefined != null) {
try {
return Class.forName(classBeingRedefined.getName(), true, classLoader) == classBeingRedefined;
} catch (Throwable ignored) {
return false;
}
} else {
return true;
}
}
/**
* Returns an inverted version of this matcher.
*
* @return An inverted version of this matcher.
*/
public RawMatcher inverted() {
return new Inversion(this);
}
}
/**
* A conjunction of two raw matchers.
*/
@HashCodeAndEqualsPlugin.Enhance
class Conjunction implements RawMatcher {
/**
* The matchers to apply in their application order.
*/
private final List matchers;
/**
* Creates a new conjunction of two raw matchers.
*
* @param matcher The matchers to apply in their application order.
*/
protected Conjunction(RawMatcher... matcher) {
this(Arrays.asList(matcher));
}
/**
* Creates a new conjunction of two raw matchers.
*
* @param matchers The matchers to apply in their application order.
*/
protected Conjunction(List extends RawMatcher> matchers) {
this.matchers = new ArrayList(matchers.size());
for (RawMatcher matcher : matchers) {
if (matcher instanceof Conjunction) {
this.matchers.addAll(((Conjunction) matcher).matchers);
} else if (matcher != Trivial.MATCHING) {
this.matchers.add(matcher);
}
}
}
/**
* {@inheritDoc}
*/
public boolean matches(TypeDescription typeDescription,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull Class> classBeingRedefined,
@MaybeNull ProtectionDomain protectionDomain) {
for (RawMatcher matcher : matchers) {
if (!matcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)) {
return false;
}
}
return true;
}
}
/**
* A disjunction of two raw matchers.
*/
@HashCodeAndEqualsPlugin.Enhance
class Disjunction implements RawMatcher {
/**
* The matchers to apply in their application order.
*/
private final List matchers;
/**
* Creates a new conjunction of two raw matchers.
*
* @param matcher The matchers to apply in their application order.
*/
protected Disjunction(RawMatcher... matcher) {
this(Arrays.asList(matcher));
}
/**
* Creates a new conjunction of two raw matchers.
*
* @param matchers The matchers to apply in their application order.
*/
protected Disjunction(List extends RawMatcher> matchers) {
this.matchers = new ArrayList(matchers.size());
for (RawMatcher matcher : matchers) {
if (matcher instanceof Disjunction) {
this.matchers.addAll(((Disjunction) matcher).matchers);
} else if (matcher != Trivial.NON_MATCHING) {
this.matchers.add(matcher);
}
}
}
/**
* {@inheritDoc}
*/
public boolean matches(TypeDescription typeDescription,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull Class> classBeingRedefined,
@MaybeNull ProtectionDomain protectionDomain) {
for (RawMatcher matcher : matchers) {
if (matcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)) {
return true;
}
}
return false;
}
}
/**
* A raw matcher that inverts a raw matcher's result.
*/
@HashCodeAndEqualsPlugin.Enhance
class Inversion implements RawMatcher {
/**
* The matcher to invert.
*/
private final RawMatcher matcher;
/**
* Creates a raw matcher that inverts its result.
*
* @param matcher The matcher to invert.
*/
public Inversion(RawMatcher matcher) {
this.matcher = matcher;
}
/**
* {@inheritDoc}
*/
public boolean matches(TypeDescription typeDescription,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull Class> classBeingRedefined,
@MaybeNull ProtectionDomain protectionDomain) {
return !matcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain);
}
}
/**
* 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.
*/
@HashCodeAndEqualsPlugin.Enhance
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.Module}.
*/
private final ElementMatcher super JavaModule> moduleMatcher;
/**
* Creates a new {@link net.bytebuddy.agent.builder.AgentBuilder.RawMatcher} that only matches the
* supplied {@link TypeDescription} against a supplied matcher.
*
* @param typeMatcher The type matcher to apply to a {@link TypeDescription}.
*/
public ForElementMatchers(ElementMatcher super TypeDescription> typeMatcher) {
this(typeMatcher, any());
}
/**
* Creates a new {@link net.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}.
*/
public ForElementMatchers(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher) {
this(typeMatcher, classLoaderMatcher, any());
}
/**
* Creates a new {@link net.bytebuddy.agent.builder.AgentBuilder.RawMatcher} that only matches the
* supplied {@link TypeDescription}, its {@link java.lang.ClassLoader} and module against element
* suitable matchers.
*
* @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.Module}.
*/
public ForElementMatchers(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher) {
this.typeMatcher = typeMatcher;
this.classLoaderMatcher = classLoaderMatcher;
this.moduleMatcher = moduleMatcher;
}
/**
* {@inheritDoc}
*/
public boolean matches(TypeDescription typeDescription,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull Class> classBeingRedefined,
@MaybeNull ProtectionDomain protectionDomain) {
return moduleMatcher.matches(module) && classLoaderMatcher.matches(classLoader) && typeMatcher.matches(typeDescription);
}
}
}
/**
* A listener that is informed about events that occur during an instrumentation process.
*/
interface Listener {
/**
* Indicates that a transformed type is loaded.
*/
boolean LOADED = true;
/**
* Invoked upon a type being supplied to a transformer.
*
* @param typeName The binary name of the instrumented type.
* @param classLoader The class loader which is loading this type or {@code null} if loaded by the boots loader.
* @param module The instrumented type's module or {@code null} if the current VM does not support modules.
* @param loaded {@code true} if the type is already loaded.
*/
void onDiscovery(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded);
/**
* Invoked prior to a successful transformation being applied.
*
* @param typeDescription The type that is being transformed.
* @param classLoader The class loader which is loading this type or {@code null} if loaded by the boots loader.
* @param module The transformed type's module or {@code null} if the current VM does not support modules.
* @param loaded {@code true} if the type is already loaded.
* @param dynamicType The dynamic type that was created.
*/
void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, 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 or {@code null} if loaded by the boots loader.
* @param module The ignored type's module or {@code null} if the current VM does not support modules.
* @param loaded {@code true} if the type is already loaded.
*/
void onIgnored(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded);
/**
* Invoked when an error has occurred during transformation.
*
* @param typeName The binary name of the instrumented type.
* @param classLoader The class loader which is loading this type or {@code null} if loaded by the boots loader.
* @param module The instrumented type's module or {@code null} if the current VM does not support modules.
* @param loaded {@code true} if the type is already loaded.
* @param throwable The occurred error.
*/
void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, 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 or {@code null} if loaded by the boots loader.
* @param module The instrumented type's module or {@code null} if the current VM does not support modules.
* @param loaded {@code true} if the type is already loaded.
*/
void onComplete(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded);
/**
* A no-op implementation of a {@link net.bytebuddy.agent.builder.AgentBuilder.Listener}.
*/
enum NoOp implements Listener {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public void onDiscovery(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onIgnored(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onComplete(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
/* do nothing */
}
}
/**
* An adapter for a listener where all methods are implemented as non-operational.
*/
abstract class Adapter implements Listener {
/**
* {@inheritDoc}
*/
public void onDiscovery(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onIgnored(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onComplete(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
/* 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.
*/
@HashCodeAndEqualsPlugin.Enhance
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 StreamWriting 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 standard error stream.
*/
public static StreamWriting toSystemError() {
return new StreamWriting(System.err);
}
/**
* Returns a version of this listener that only reports successfully transformed classes and failed transformations.
*
* @return A version of this listener that only reports successfully transformed classes and failed transformations.
*/
public Listener withTransformationsOnly() {
return new WithTransformationsOnly(this);
}
/**
* Returns a version of this listener that only reports failed transformations.
*
* @return A version of this listener that only reports failed transformations.
*/
public Listener withErrorsOnly() {
return new WithErrorsOnly(this);
}
/**
* {@inheritDoc}
*/
public void onDiscovery(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
printStream.printf(PREFIX + " DISCOVERY %s [%s, %s, %s, loaded=%b]%n", typeName, classLoader, module, Thread.currentThread(), loaded);
}
/**
* {@inheritDoc}
*/
public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) {
printStream.printf(PREFIX + " TRANSFORM %s [%s, %s, %s, loaded=%b]%n", typeDescription.getName(), classLoader, module, Thread.currentThread(), loaded);
}
/**
* {@inheritDoc}
*/
public void onIgnored(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
printStream.printf(PREFIX + " IGNORE %s [%s, %s, %s, loaded=%b]%n", typeDescription.getName(), classLoader, module, Thread.currentThread(), loaded);
}
/**
* {@inheritDoc}
*/
public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) {
synchronized (printStream) {
printStream.printf(PREFIX + " ERROR %s [%s, %s, %s, loaded=%b]%n", typeName, classLoader, module, Thread.currentThread(), loaded);
throwable.printStackTrace(printStream);
}
}
/**
* {@inheritDoc}
*/
public void onComplete(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
printStream.printf(PREFIX + " COMPLETE %s [%s, %s, %s, loaded=%b]%n", typeName, classLoader, module, Thread.currentThread(), loaded);
}
}
/**
* A listener that filters types with a given name from being logged.
*/
@HashCodeAndEqualsPlugin.Enhance
class Filtering implements Listener {
/**
* The matcher to decide upon a type should be logged.
*/
private final ElementMatcher super String> matcher;
/**
* The delegate listener.
*/
private final Listener delegate;
/**
* Creates a new filtering listener.
*
* @param matcher The matcher to decide upon a type should be logged.
* @param delegate The delegate listener.
*/
public Filtering(ElementMatcher super String> matcher, Listener delegate) {
this.matcher = matcher;
this.delegate = delegate;
}
/**
* {@inheritDoc}
*/
public void onDiscovery(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
if (matcher.matches(typeName)) {
delegate.onDiscovery(typeName, classLoader, module, loaded);
}
}
/**
* {@inheritDoc}
*/
public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) {
if (matcher.matches(typeDescription.getName())) {
delegate.onTransformation(typeDescription, classLoader, module, loaded, dynamicType);
}
}
/**
* {@inheritDoc}
*/
public void onIgnored(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
if (matcher.matches(typeDescription.getName())) {
delegate.onIgnored(typeDescription, classLoader, module, loaded);
}
}
/**
* {@inheritDoc}
*/
public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) {
if (matcher.matches(typeName)) {
delegate.onError(typeName, classLoader, module, loaded, throwable);
}
}
/**
* {@inheritDoc}
*/
public void onComplete(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
if (matcher.matches(typeName)) {
delegate.onComplete(typeName, classLoader, module, loaded);
}
}
}
/**
* A listener that only delegates events if they are successful or failed transformations.
*/
@HashCodeAndEqualsPlugin.Enhance
class WithTransformationsOnly extends Adapter {
/**
* The delegate listener.
*/
private final Listener delegate;
/**
* Creates a new listener that only delegates events if they are successful or failed transformations.
*
* @param delegate The delegate listener.
*/
public WithTransformationsOnly(Listener delegate) {
this.delegate = delegate;
}
@Override
public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) {
delegate.onTransformation(typeDescription, classLoader, module, loaded, dynamicType);
}
@Override
public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) {
delegate.onError(typeName, classLoader, module, loaded, throwable);
}
}
/**
* A listener that only delegates events if they are failed transformations.
*/
@HashCodeAndEqualsPlugin.Enhance
class WithErrorsOnly extends Adapter {
/**
* The delegate listener.
*/
private final Listener delegate;
/**
* Creates a new listener that only delegates events if they are failed transformations.
*
* @param delegate The delegate listener.
*/
public WithErrorsOnly(Listener delegate) {
this.delegate = delegate;
}
@Override
public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) {
delegate.onError(typeName, classLoader, module, loaded, throwable);
}
}
/**
* A listener that adds read-edges to any module of an instrumented class upon its transformation and opens the class's package to the module.
*/
@HashCodeAndEqualsPlugin.Enhance
class ModuleReadEdgeCompleting extends 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.
* This will also ensure that the package of the instrumented type is exported to the target 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. This will also ensure that the package
* of the instrumented type is exported to the target 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. This will also ensure that the package
* of the instrumented type is exported to the target module.
* @param type The types for which to extract the modules.
* @return An appropriate listener.
*/
public static Listener of(Instrumentation instrumentation, boolean addTargetEdge, Class>... type) {
Set modules = new HashSet();
for (Class> aType : type) {
modules.add(JavaModule.ofType(aType));
}
return modules.isEmpty()
? Listener.NoOp.INSTANCE
: new Listener.ModuleReadEdgeCompleting(instrumentation, addTargetEdge, modules);
}
@Override
public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) {
if (module != JavaModule.UNSUPPORTED && module.isNamed()) {
for (JavaModule target : modules) {
if (!module.canRead(target) || addTargetEdge && !module.isOpened(typeDescription.getPackage(), target)) {
PackageDescription location = typeDescription.getPackage();
ClassInjector.UsingInstrumentation.redefineModule(instrumentation,
module,
Collections.singleton(target),
Collections.>emptyMap(),
!addTargetEdge || location == null || location.isDefault()
? Collections.>emptyMap()
: Collections.singletonMap(location.getName(), Collections.singleton(target)),
Collections.>emptySet(),
Collections., List>>emptyMap());
}
if (addTargetEdge && !target.canRead(module)) {
ClassInjector.UsingInstrumentation.redefineModule(instrumentation,
target,
Collections.singleton(module),
Collections.>emptyMap(),
Collections.>emptyMap(),
Collections.>emptySet(),
Collections., List>>emptyMap());
}
}
}
}
}
/**
* A compound listener that allows to group several listeners in one instance.
*/
@HashCodeAndEqualsPlugin.Enhance
class Compound implements Listener {
/**
* The listeners that are represented by this compound listener in their application order.
*/
private final List 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 = new ArrayList();
for (Listener listener : listeners) {
if (listener instanceof Compound) {
this.listeners.addAll(((Compound) listener).listeners);
} else if (!(listener instanceof NoOp)) {
this.listeners.add(listener);
}
}
}
/**
* {@inheritDoc}
*/
public void onDiscovery(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
for (Listener listener : listeners) {
listener.onDiscovery(typeName, classLoader, module, loaded);
}
}
/**
* {@inheritDoc}
*/
public void onTransformation(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, DynamicType dynamicType) {
for (Listener listener : listeners) {
listener.onTransformation(typeDescription, classLoader, module, loaded, dynamicType);
}
}
/**
* {@inheritDoc}
*/
public void onIgnored(TypeDescription typeDescription, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
for (Listener listener : listeners) {
listener.onIgnored(typeDescription, classLoader, module, loaded);
}
}
/**
* {@inheritDoc}
*/
public void onError(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded, Throwable throwable) {
for (Listener listener : listeners) {
listener.onError(typeName, classLoader, module, loaded, throwable);
}
}
/**
* {@inheritDoc}
*/
public void onComplete(String typeName, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module, boolean loaded) {
for (Listener listener : listeners) {
listener.onComplete(typeName, classLoader, module, loaded);
}
}
}
}
/**
* A circularity lock is responsible for preventing that a {@link ClassFileLocator} is used recursively.
* This can happen when a class file transformation causes another class to be loaded. Without avoiding
* such circularities, a class loading is aborted by a {@link ClassCircularityError} which causes the
* class loading to fail.
*/
interface CircularityLock {
/**
* Attempts to acquire a circularity lock.
*
* @return {@code true} if the lock was acquired successfully, {@code false} if it is already hold.
*/
boolean acquire();
/**
* Releases the circularity lock if it is currently acquired.
*/
void release();
/**
* An inactive circularity lock which is always acquirable.
*/
enum Inactive implements CircularityLock {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public boolean acquire() {
return true;
}
/**
* {@inheritDoc}
*/
public void release() {
/* do nothing */
}
}
/**
* A circularity lock that surrounds the locking mechanism with a global lock to prevent that the
* locking mechanism itself loads classes and causes a circularity issue.
*/
abstract class WithInnerClassLoadingLock implements CircularityLock {
/**
* The default size of the global class loading lock array.
*/
protected static final int DEFAULT_SIZE = 100;
/**
* An additional global lock that avoids circularity errors cause by class loading
* by the locking mechanism.
*/
private final TrivialLock[] lock;
/**
* Creates a circularity lock with a global outer lock.
*
* @param size The amount of locks used in parallel or {@code 0} if no global locks should be used.
*/
protected WithInnerClassLoadingLock(int size) {
lock = new TrivialLock[size];
for (int index = 0; index < size; index++) {
this.lock[index] = new TrivialLock();
}
}
/**
* {@inheritDoc}
*/
public boolean acquire() {
if (lock.length == 0) {
return doAcquire();
}
TrivialLock lock;
if (this.lock.length == 1) {
lock = this.lock[0];
} else {
int hash = System.identityHashCode(Thread.currentThread());
lock = this.lock[hash == Integer.MIN_VALUE ? 0 : Math.abs(hash) % this.lock.length];
}
synchronized (lock) { // avoid that different class loaders skip class loading of each other
if (lock.locked) {
return false;
} else {
lock.locked = true;
try {
return doAcquire();
} finally {
lock.locked = false;
}
}
}
}
/**
* Acquires the actual lock for the current thread.
*
* @return {@code true} if the lock was acquired successfully, {@code false} if it is already hold.
*/
protected abstract boolean doAcquire();
/**
* A trivial lock that monitors if a class is currently loaded by the current thread.
*/
protected static class TrivialLock {
/**
* If {@code true}, a class is currently loaded by the current lock.
*/
protected boolean locked;
}
}
/**
* A default implementation of a circularity lock. Since class loading already synchronizes on a class loader,
* it suffices to apply a thread-local lock.
*/
class Default extends WithInnerClassLoadingLock {
/**
* A map of threads to an unused boolean to emulate a thread-local state without using
* thread locals. This avoids using thread-local maps and does not interfere with Java
* fibers in case that an instrumentation is executed from a virtual thread where thread
* locals are not permitted.
*/
private final ConcurrentMap threads = new ConcurrentHashMap();
/**
* Creates a default lock with a default size for the amount of global locks.
*/
public Default() {
super(DEFAULT_SIZE);
}
/**
* Creates a default lock with the supplied number of global locks.
*
* @param size The amount of locks used in parallel or {@code 0} if no global locks should be used.
*/
public Default(int size) {
super(size);
}
@Override
protected boolean doAcquire() {
return threads.putIfAbsent(Thread.currentThread(), true) == null;
}
/**
* {@inheritDoc}
*/
public void release() {
threads.remove(Thread.currentThread());
}
/**
* Returns {@code true} if the current thread is currently locked.
*
* @return {@code true} if the current thread is currently locked.
*/
protected boolean isLocked() {
return threads.containsKey(Thread.currentThread());
}
}
/**
* A circularity lock that holds a global monitor and does not permit concurrent access.
*/
@HashCodeAndEqualsPlugin.Enhance
class Global extends WithInnerClassLoadingLock {
/**
* The lock to hold.
*/
private final Lock lock;
/**
* The time to wait for the lock.
*/
private final long time;
/**
* The time's time unit.
*/
private final TimeUnit timeUnit;
/**
* Creates a new global circularity lock that does not wait for a release and a
* default size for the amount of global locks.
*/
public Global() {
this(DEFAULT_SIZE);
}
/**
* Creates a new global circularity lock with a default size for the amount of global locks.
*
* @param time The time to wait for the lock.
* @param timeUnit The time's time unit.
*/
public Global(long time, TimeUnit timeUnit) {
this(DEFAULT_SIZE, time, timeUnit);
}
/**
* Creates a new global circularity lock that does not wait for a release.
*
* @param size The amount of locks used in parallel or {@code 0} if no global locks should be used.
*/
public Global(int size) {
this(size, 0, TimeUnit.MILLISECONDS);
}
/**
* Creates a new global circularity lock.
*
* @param size The amount of locks used in parallel or {@code 0} if no global locks should be used.
* @param time The time to wait for the lock.
* @param timeUnit The time's time unit.
*/
public Global(int size, long time, TimeUnit timeUnit) {
super(size);
lock = new ReentrantLock();
this.time = time;
this.timeUnit = timeUnit;
}
@Override
protected boolean doAcquire() {
try {
return time == 0
? lock.tryLock()
: lock.tryLock(time, timeUnit);
} catch (InterruptedException ignored) {
return false;
}
}
/**
* {@inheritDoc}
*/
public void release() {
lock.unlock();
}
}
}
/**
* 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.
* @param classLoader The instrumented type's class loader or {@code null} if the type is loaded by the bootstrap loader.
* @param module The instrumented type's module or {@code null} if it is not declared by a module.
* @param protectionDomain The instrumented type's protection domain or {@code null} if it does not define a protection domain.
* @return A type builder for the given arguments.
*/
DynamicType.Builder> builder(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain);
/**
* Default implementations of type strategies.
*/
enum Default implements TypeStrategy {
/**
* A definition handler that performs a rebasing for all types.
*/
REBASE {
/** {@inheritDoc} */
public DynamicType.Builder> builder(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain) {
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 {
/** {@inheritDoc} */
public DynamicType.Builder> builder(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain) {
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)}.
* Using this strategy also configures Byte Buddy to create frozen instrumented types and discards any explicit configuration.
*
*
* For prohibiting any changes on a class file, use {@link AgentBuilder#disableClassFormatChanges()}
*
*/
REDEFINE_FROZEN {
/** {@inheritDoc} */
public DynamicType.Builder> builder(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain) {
return byteBuddy.with(InstrumentedType.Factory.Default.FROZEN)
.with(VisibilityBridgeStrategy.Default.NEVER)
.redefine(typeDescription, classFileLocator)
.ignoreAlso(LatentMatcher.ForSelfDeclaredMethod.NOT_DECLARED);
}
},
/**
*
* A definition handler that performs a decoration of declared methods only. Using this type strategy
* implies the limitations that are described by {@link ByteBuddy#decorate(TypeDescription, ClassFileLocator)}.
* This type strategy can be useful when only applying {@link AsmVisitorWrapper}s without attempting to change
* the class file layout..
*
*
* 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)}.
* Using this strategy also configures Byte Buddy to create frozen instrumented types and discards any explicit configuration.
*
*
* For prohibiting any changes on a class file, use {@link AgentBuilder#disableClassFormatChanges()}
*
*/
DECORATE {
/** {@inheritDoc} */
public DynamicType.Builder> builder(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain) {
return byteBuddy.decorate(typeDescription, classFileLocator);
}
}
}
/**
* A type strategy that applies a build {@link EntryPoint}.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForBuildEntryPoint implements TypeStrategy {
/**
* The entry point to apply.
*/
private final EntryPoint entryPoint;
/**
* Creates a new type strategy for an entry point.
*
* @param entryPoint The entry point to apply.
*/
public ForBuildEntryPoint(EntryPoint entryPoint) {
this.entryPoint = entryPoint;
}
/**
* {@inheritDoc}
*/
public DynamicType.Builder> builder(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain) {
return entryPoint.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer);
}
}
}
/**
* A transformer allows to apply modifications to a {@link net.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 net.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.
* @param module The class's module or {@code null} if the current VM does not support modules.
* @param protectionDomain The protection domain of the transformed type or {@code null} if not available
* @return A transformed version of the supplied {@code builder}.
*/
DynamicType.Builder> transform(DynamicType.Builder> builder,
TypeDescription typeDescription,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain);
/**
* A transformer that applies a build {@link Plugin}. Note that a transformer is never completed as class loading
* might happen dynamically such that plugins are not closed.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForBuildPlugin implements Transformer {
/**
* The plugin to apply.
*/
private final Plugin plugin;
/**
* Creates a new transformer for a build {@link Plugin}.
*
* @param plugin The plugin to apply.
*/
public ForBuildPlugin(Plugin plugin) {
this.plugin = plugin;
}
/**
* {@inheritDoc}
*/
public DynamicType.Builder> transform(DynamicType.Builder> builder,
TypeDescription typeDescription,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain) {
return plugin.apply(builder, typeDescription, ClassFileLocator.ForClassLoader.of(classLoader));
}
}
/**
* A transformer for applying an {@link Advice} where this advice class might reference types of both the agent's and the user's
* class loader. Using this transformer, it is possible to apply advice without including any library dependencies of this advice
* class which are then rather looked up from the transformed class's class loader. For this to work, it is required to register
* the advice class's class loader manually via the {@code include} methods and to reference the advice class by its fully-qualified
* name. The advice class is then never loaded by rather described by a {@link TypePool}.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForAdvice implements Transformer {
/**
* The advice to use.
*/
private final Advice.WithCustomMapping advice;
/**
* The exception handler to register for the advice.
*/
private final Advice.ExceptionHandler exceptionHandler;
/**
* The assigner to use for the advice.
*/
private final Assigner assigner;
/**
* The class file locator to query for the advice class.
*/
private final ClassFileLocator classFileLocator;
/**
* The pool strategy to use for looking up an advice.
*/
private final PoolStrategy poolStrategy;
/**
* The location strategy to use for class loaders when resolving advice classes.
*/
private final LocationStrategy locationStrategy;
/**
* The advice entries to apply.
*/
private final List entries;
/**
* Creates a new advice transformer with a default setup.
*/
public ForAdvice() {
this(Advice.withCustomMapping());
}
/**
* Creates a new advice transformer which applies the given advice.
*
* @param advice The configured advice to use.
*/
public ForAdvice(Advice.WithCustomMapping advice) {
this(advice,
Advice.ExceptionHandler.Default.SUPPRESSING,
Assigner.DEFAULT,
ClassFileLocator.NoOp.INSTANCE,
PoolStrategy.Default.FAST,
LocationStrategy.ForClassLoader.STRONG,
Collections.emptyList());
}
/**
* Creates a new advice transformer.
*
* @param advice The configured advice to use.
* @param exceptionHandler The exception handler to use.
* @param assigner The assigner to use.
* @param classFileLocator The class file locator to use.
* @param poolStrategy The pool strategy to use for looking up an advice.
* @param locationStrategy The location strategy to use for class loaders when resolving advice classes.
* @param entries The advice entries to apply.
*/
protected ForAdvice(Advice.WithCustomMapping advice,
Advice.ExceptionHandler exceptionHandler,
Assigner assigner,
ClassFileLocator classFileLocator,
PoolStrategy poolStrategy,
LocationStrategy locationStrategy,
List entries) {
this.advice = advice;
this.exceptionHandler = exceptionHandler;
this.assigner = assigner;
this.classFileLocator = classFileLocator;
this.poolStrategy = poolStrategy;
this.locationStrategy = locationStrategy;
this.entries = entries;
}
/**
* {@inheritDoc}
*/
public DynamicType.Builder> transform(DynamicType.Builder> builder,
TypeDescription typeDescription,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain) {
ClassFileLocator classFileLocator = new ClassFileLocator.Compound(this.classFileLocator, locationStrategy.classFileLocator(classLoader, module));
TypePool typePool = poolStrategy.typePool(classFileLocator, classLoader);
AsmVisitorWrapper.ForDeclaredMethods asmVisitorWrapper = new AsmVisitorWrapper.ForDeclaredMethods();
for (Entry entry : entries) {
asmVisitorWrapper = asmVisitorWrapper.invokable(entry.getMatcher().resolve(typeDescription), wrap(typeDescription,
classLoader,
module,
protectionDomain,
entry.resolve(advice, typePool, classFileLocator).withAssigner(assigner).withExceptionHandler(exceptionHandler)));
}
return builder.visit(asmVisitorWrapper);
}
/**
* Allows for decoration of advice for subclass implementations of this transformer. Note that a subclass is not retained when
* using the builder methods of this class. Subclasses should also override the {@code make} method of this class to allow
* propagating the custom configuration.
*
* @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.
* @param module The class's module or {@code null} if the current VM does not support modules.
* @param protectionDomain The protection domain of the transformed type or {@code null} if not available
* @param advice The advice to wrap.
* @return A visitor wrapper that represents the supplied advice.
*/
@SuppressWarnings("unused")
protected AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper wrap(TypeDescription typeDescription,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain,
Advice advice) {
return advice;
}
/**
* Creates an advice transformer. This method is to be overridden when overriding the {@code wrap} method.
*
* @param advice The configured advice to use.
* @param exceptionHandler The exception handler to use.
* @param assigner The assigner to use.
* @param classFileLocator The class file locator to use.
* @param poolStrategy The pool strategy to use for looking up an advice.
* @param locationStrategy The location strategy to use for class loaders when resolving advice classes.
* @param entries The advice entries to apply.
* @return An appropriate advice transformer.
*/
protected ForAdvice make(Advice.WithCustomMapping advice,
Advice.ExceptionHandler exceptionHandler,
Assigner assigner,
ClassFileLocator classFileLocator,
PoolStrategy poolStrategy,
LocationStrategy locationStrategy,
List entries) {
return new ForAdvice(advice, exceptionHandler, assigner, classFileLocator, poolStrategy, locationStrategy, entries);
}
/**
* Registers a pool strategy for creating a {@link TypePool} that should be used for creating the advice class.
*
* @param poolStrategy The pool strategy to use.
* @return A new instance of this advice transformer that applies the supplied pool strategy.
*/
public ForAdvice with(PoolStrategy poolStrategy) {
return make(advice, exceptionHandler, assigner, classFileLocator, poolStrategy, locationStrategy, entries);
}
/**
* Registers a location strategy for creating a {@link ClassFileLocator} from the class loader that is supplied during transformation
* that should be used for looking up advice-relevant classes.
*
* @param locationStrategy The location strategy to use.
* @return A new instance of this advice transformer that applies the supplied location strategy.
*/
public ForAdvice with(LocationStrategy locationStrategy) {
return make(advice, exceptionHandler, assigner, classFileLocator, poolStrategy, locationStrategy, entries);
}
/**
* Registers an exception handler for suppressed exceptions to use by the registered advice.
*
* @param exceptionHandler The exception handler to use.
* @return A new instance of this advice transformer that applies the supplied exception handler.
* @see Advice#withExceptionHandler(StackManipulation)
*/
public ForAdvice withExceptionHandler(Advice.ExceptionHandler exceptionHandler) {
return make(advice, exceptionHandler, assigner, classFileLocator, poolStrategy, locationStrategy, entries);
}
/**
* Registers an assigner to be used by the advice class.
*
* @param assigner The assigner to use.
* @return A new instance of this advice transformer that applies the supplied assigner.
* @see Advice#withAssigner(Assigner)
*/
public ForAdvice with(Assigner assigner) {
return make(advice, exceptionHandler, assigner, classFileLocator, poolStrategy, locationStrategy, entries);
}
/**
* Includes the supplied class loaders as a source for looking up an advice class or its dependencies.
* Note that the supplied class loaders are queried for types before the class loader of the instrumented class.
*
* @param classLoader The class loaders to include when looking up classes in their order. Duplicates are filtered.
* @return A new instance of this advice transformer that considers the supplied class loaders as a lookup source.
*/
public ForAdvice include(ClassLoader... classLoader) {
Set classFileLocators = new LinkedHashSet();
for (ClassLoader aClassLoader : classLoader) {
classFileLocators.add(ClassFileLocator.ForClassLoader.of(aClassLoader));
}
return include(new ArrayList(classFileLocators));
}
/**
* Includes the supplied class file locators as a source for looking up an advice class or its dependencies.
* Note that the supplied class loaders are queried for types before the class loader of the instrumented class.
*
* @param classFileLocator The class file locators to include when looking up classes in their order. Duplicates are filtered.
* @return A new instance of this advice transformer that considers the supplied class file locators as a lookup source.
*/
public ForAdvice include(ClassFileLocator... classFileLocator) {
return include(Arrays.asList(classFileLocator));
}
/**
* Includes the supplied class file locators as a source for looking up an advice class or its dependencies.
* Note that the supplied class loaders are queried for types before the class loader of the instrumented class.
*
* @param classFileLocators The class file locators to include when looking up classes in their order. Duplicates are filtered.
* @return A new instance of this advice transformer that considers the supplied class file locators as a lookup source.
*/
public ForAdvice include(List extends ClassFileLocator> classFileLocators) {
return make(advice,
exceptionHandler,
assigner,
new ClassFileLocator.Compound(CompoundList.of(classFileLocator, classFileLocators)),
poolStrategy,
locationStrategy,
entries);
}
/**
* Applies the given advice class onto all methods that satisfy the supplied matcher.
*
* @param matcher The matcher to determine what methods the advice should be applied to.
* @param name The fully-qualified, binary name of the advice class.
* @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type.
*/
public ForAdvice advice(ElementMatcher super MethodDescription> matcher, String name) {
return advice(new LatentMatcher.Resolved(matcher), name);
}
/**
* Applies the given advice class onto all methods that satisfy the supplied matcher.
*
* @param matcher The matcher to determine what methods the advice should be applied to.
* @param name The fully-qualified, binary name of the advice class.
* @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type.
*/
public ForAdvice advice(LatentMatcher super MethodDescription> matcher, String name) {
return make(advice,
exceptionHandler,
assigner,
classFileLocator,
poolStrategy,
locationStrategy,
CompoundList.of(entries, new Entry.ForUnifiedAdvice(matcher, name)));
}
/**
* Applies the given advice class onto all methods that satisfy the supplied matcher.
*
* @param matcher The matcher to determine what methods the advice should be applied to.
* @param enter The fully-qualified, binary name of the enter advice class.
* @param exit The fully-qualified, binary name of the exit advice class.
* @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type.
*/
public ForAdvice advice(ElementMatcher super MethodDescription> matcher, String enter, String exit) {
return advice(new LatentMatcher.Resolved(matcher), enter, exit);
}
/**
* Applies the given advice class onto all methods that satisfy the supplied matcher.
*
* @param matcher The matcher to determine what methods the advice should be applied to.
* @param enter The fully-qualified, binary name of the enter advice class.
* @param exit The fully-qualified, binary name of the exit advice class.
* @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type.
*/
public ForAdvice advice(LatentMatcher super MethodDescription> matcher, String enter, String exit) {
return make(advice,
exceptionHandler,
assigner,
classFileLocator,
poolStrategy,
locationStrategy,
CompoundList.of(entries, new Entry.ForSplitAdvice(matcher, enter, exit)));
}
/**
* An entry for an advice to apply.
*/
@HashCodeAndEqualsPlugin.Enhance
protected abstract static class Entry {
/**
* The matcher for advised methods.
*/
private final LatentMatcher super MethodDescription> matcher;
/**
* Creates a new entry.
*
* @param matcher The matcher for advised methods.
*/
protected Entry(LatentMatcher super MethodDescription> matcher) {
this.matcher = matcher;
}
/**
* Returns the matcher for advised methods.
*
* @return The matcher for advised methods.
*/
protected LatentMatcher super MethodDescription> getMatcher() {
return matcher;
}
/**
* Resolves the advice for this entry.
*
* @param advice The advice configuration.
* @param typePool The type pool to use.
* @param classFileLocator The class file locator to use.
* @return The resolved advice.
*/
protected abstract Advice resolve(Advice.WithCustomMapping advice, TypePool typePool, ClassFileLocator classFileLocator);
/**
* An entry for an advice class where both the (optional) entry and exit advice methods are declared by the same class.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class ForUnifiedAdvice extends Entry {
/**
* The name of the advice class.
*/
protected final String name;
/**
* Creates a new entry for an advice class where both the (optional) entry and exit advice methods are declared by the same class.
*
* @param matcher The matcher for advised methods.
* @param name The name of the advice class.
*/
protected ForUnifiedAdvice(LatentMatcher super MethodDescription> matcher, String name) {
super(matcher);
this.name = name;
}
@Override
protected Advice resolve(Advice.WithCustomMapping advice, TypePool typePool, ClassFileLocator classFileLocator) {
return advice.to(typePool.describe(name).resolve(), classFileLocator);
}
}
/**
* An entry for an advice class where both entry and exit advice methods are declared by the different classes.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class ForSplitAdvice extends Entry {
/**
* The fully-qualified, binary name of the enter advice class.
*/
private final String enter;
/**
* The fully-qualified, binary name of the exit advice class.
*/
private final String exit;
/**
* Creates a new entry for an advice class with explicit entry and exit advice classes.
*
* @param matcher The matcher for advised methods.
* @param enter The fully-qualified, binary name of the enter advice class.
* @param exit The fully-qualified, binary name of the exit advice class.
*/
protected ForSplitAdvice(LatentMatcher super MethodDescription> matcher, String enter, String exit) {
super(matcher);
this.enter = enter;
this.exit = exit;
}
@Override
protected Advice resolve(Advice.WithCustomMapping advice, TypePool typePool, ClassFileLocator classFileLocator) {
return advice.to(typePool.describe(enter).resolve(), typePool.describe(exit).resolve(), classFileLocator);
}
}
}
}
}
/**
* A type locator allows to specify how {@link TypeDescription}s are resolved by an {@link net.bytebuddy.agent.builder.AgentBuilder}.
*/
interface PoolStrategy {
/**
* 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 or {@code null} if the boot loader.
* @return A type pool for the supplied class file locator.
*/
TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader);
/**
* Creates a type pool for a given class file locator. If a cache is used, the type that is
* currently instrumented is not used.
*
* @param classFileLocator The class file locator to use.
* @param classLoader The class loader for which the class file locator
* was created or {@code null} if the boot loader.
* @param name The name of the currently instrumented type.
* @return A type pool for the supplied class file locator.
*/
TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader, String name);
/**
*
* A default type locator that resolves types only if any property that is not the type's name is requested.
*
*
* The returned type pool uses a {@link net.bytebuddy.pool.TypePool.CacheProvider.Simple} and the
* {@link ClassFileLocator} that is provided by the builder's {@link LocationStrategy}.
*
*/
enum Default implements PoolStrategy {
/**
* 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 net.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 net.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;
}
/**
* {@inheritDoc}
*/
public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader) {
return new TypePool.LazyFacade(new TypePool.Default.WithLazyResolution(TypePool.CacheProvider.Simple.withObjectType(),
classFileLocator,
readerMode));
}
/**
* {@inheritDoc}
*/
public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader, String name) {
return typePool(classFileLocator, classLoader);
}
}
/**
*
* A type locator that resolves all type descriptions eagerly.
*
*
* The returned type pool uses a {@link net.bytebuddy.pool.TypePool.CacheProvider.Simple} and the
* {@link ClassFileLocator} that is provided by the builder's {@link LocationStrategy}.
*
*/
enum Eager implements PoolStrategy {
/**
* 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 net.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 net.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.
*/
Eager(TypePool.Default.ReaderMode readerMode) {
this.readerMode = readerMode;
}
/**
* {@inheritDoc}
*/
public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader) {
return new TypePool.Default(TypePool.CacheProvider.Simple.withObjectType(), classFileLocator, readerMode);
}
/**
* {@inheritDoc}
*/
public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader, String name) {
return typePool(classFileLocator, classLoader);
}
}
/**
*
* A type locator that attempts loading a type if it cannot be located by the underlying lazy type pool.
*
*
* The returned type pool uses a {@link net.bytebuddy.pool.TypePool.CacheProvider.Simple} and the
* {@link ClassFileLocator} that is provided by the builder's {@link LocationStrategy}. Any types
* are loaded via the instrumented type's {@link ClassLoader}.
*
*/
enum ClassLoading implements PoolStrategy {
/**
* 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 net.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 net.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.
*/
ClassLoading(TypePool.Default.ReaderMode readerMode) {
this.readerMode = readerMode;
}
/**
* {@inheritDoc}
*/
public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader) {
return TypePool.ClassLoading.of(classLoader, new TypePool.Default.WithLazyResolution(TypePool.CacheProvider.Simple.withObjectType(), classFileLocator, readerMode));
}
/**
* {@inheritDoc}
*/
public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader, String name) {
return typePool(classFileLocator, classLoader);
}
}
/**
*
* 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.
*
*
* All types that are returned by the locator's type pool are resolved lazily.
*
*/
@HashCodeAndEqualsPlugin.Enhance
abstract class WithTypePoolCache implements PoolStrategy {
/**
* 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 net.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;
}
/**
* {@inheritDoc}
*/
public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader) {
return new TypePool.LazyFacade(new TypePool.Default.WithLazyResolution(locate(classLoader), classFileLocator, readerMode));
}
/**
* {@inheritDoc}
*/
public TypePool typePool(ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader, String name) {
return new TypePool.LazyFacade(new TypePool.Default.WithLazyResolution(new TypePool.CacheProvider.Discriminating(ElementMatchers.is(name),
new TypePool.CacheProvider.Simple(),
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(@MaybeNull ClassLoader classLoader);
/**
* 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.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Simple extends WithTypePoolCache {
/**
* A default value for marking the boostrap class loader.
*/
private static final ClassLoader BOOTSTRAP_MARKER = doPrivileged(BootstrapMarkerAction.INSTANCE);
/**
* 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 net.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;
}
/**
* A proxy for {@code java.security.AccessController#doPrivileged} that is activated if available.
*
* @param action The action to execute from a privileged context.
* @param The type of the action's resolved value.
* @return The action's resolved value.
*/
@AccessControllerPlugin.Enhance
private static T doPrivileged(PrivilegedAction action) {
return action.run();
}
@Override
protected TypePool.CacheProvider locate(@MaybeNull ClassLoader classLoader) {
classLoader = classLoader == null ? getBootstrapMarkerLoader() : classLoader;
TypePool.CacheProvider cacheProvider = cacheProviders.get(classLoader);
while (cacheProvider == null) {
cacheProvider = TypePool.CacheProvider.Simple.withObjectType();
TypePool.CacheProvider previous = cacheProviders.putIfAbsent(classLoader, cacheProvider);
if (previous != null) {
cacheProvider = previous;
}
}
return cacheProvider;
}
/**
*
* Returns the class loader to serve as a cache key if a cache provider for the bootstrap class loader is requested.
* This class loader is represented by {@code null} in the JVM which is an invalid value for many {@link ConcurrentMap}
* implementations.
*
*
* By default, a custom class loader is created to use as a marker.
*
*
* @return A class loader to represent the bootstrap class loader.
*/
protected ClassLoader getBootstrapMarkerLoader() {
return BOOTSTRAP_MARKER;
}
/**
* An action that creates a class loader to mark the bootstrap loader without using {@code null}.
*/
protected enum BootstrapMarkerAction implements PrivilegedAction {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public ClassLoader run() {
return new URLClassLoader(new URL[0], ClassLoadingStrategy.BOOTSTRAP_LOADER);
}
}
}
}
}
/**
* An initialization strategy which determines the handling of {@link net.bytebuddy.implementation.LoadedTypeInitializer}s
* and the loading of auxiliary types. The agent builder does not reuse the {@link TypeResolutionStrategy} as Javaagents cannot access
* a loaded class after a transformation such that different initialization strategies become meaningful.
*/
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 which can be {@code null} to represent the bootstrap class loader.
* @param protectionDomain The instrumented type's protection domain or {@code null} if no protection domain is available.
* @param injectionStrategy The injection strategy to use.
*/
void register(DynamicType dynamicType,
@MaybeNull ClassLoader classLoader,
@MaybeNull ProtectionDomain protectionDomain,
InjectionStrategy injectionStrategy);
}
/**
* A non-initializing initialization strategy.
*/
enum NoOp implements InitializationStrategy, Dispatcher {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Dispatcher dispatcher() {
return this;
}
/**
* {@inheritDoc}
*/
public DynamicType.Builder> apply(DynamicType.Builder> builder) {
return builder;
}
/**
* {@inheritDoc}
*/
public void register(DynamicType dynamicType,
@MaybeNull ClassLoader classLoader,
@MaybeNull ProtectionDomain protectionDomain,
InjectionStrategy injectionStrategy) {
/* do nothing */
}
}
/**
* 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;
/**
* {@inheritDoc}
*/
public Dispatcher dispatcher() {
return this;
}
/**
* {@inheritDoc}
*/
public DynamicType.Builder> apply(DynamicType.Builder> builder) {
return builder;
}
/**
* {@inheritDoc}
*/
public void register(DynamicType dynamicType,
@MaybeNull ClassLoader classLoader,
@MaybeNull ProtectionDomain protectionDomain,
InjectionStrategy injectionStrategy) {
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 = injectionStrategy.resolve(classLoader, protectionDomain);
Map loadedTypeInitializers = dynamicType.getLoadedTypeInitializers();
for (Map.Entry> entry : classInjector.inject(independentTypes).entrySet()) {
loadedTypeInitializers.get(entry.getKey()).onLoad(entry.getValue());
}
}
}
}
/**
* 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.
*/
@HashCodeAndEqualsPlugin.Enhance
abstract class SelfInjection implements InitializationStrategy {
/**
* The nexus accessor to use.
*/
protected final NexusAccessor nexusAccessor;
/**
* Creates a new self-injection strategy.
*
* @param nexusAccessor The nexus accessor to use.
*/
protected SelfInjection(NexusAccessor nexusAccessor) {
this.nexusAccessor = nexusAccessor;
}
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "DMI_RANDOM_USED_ONLY_ONCE", justification = "Avoids thread-contention.")
public InitializationStrategy.Dispatcher dispatcher() {
return dispatcher(new Random().nextInt());
}
/**
* Creates a new dispatcher.
*
* @param identification The identification code to use.
* @return An appropriate dispatcher for an initialization strategy.
*/
protected abstract InitializationStrategy.Dispatcher dispatcher(int identification);
/**
* A dispatcher for a self-initialization strategy.
*/
@HashCodeAndEqualsPlugin.Enhance
protected abstract static class Dispatcher implements InitializationStrategy.Dispatcher {
/**
* The nexus accessor to use.
*/
protected final NexusAccessor nexusAccessor;
/**
* A random identification for the applied self-initialization.
*/
protected final int identification;
/**
* Creates a new dispatcher.
*
* @param nexusAccessor The nexus accessor to use.
* @param identification A random identification for the applied self-initialization.
*/
protected Dispatcher(NexusAccessor nexusAccessor, int identification) {
this.nexusAccessor = nexusAccessor;
this.identification = identification;
}
/**
* {@inheritDoc}
*/
public DynamicType.Builder> apply(DynamicType.Builder> builder) {
return builder.initializer(new NexusAccessor.InitializationAppender(identification));
}
/**
* A type initializer that injects all auxiliary types of the instrumented type.
*/
@HashCodeAndEqualsPlugin.Enhance
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;
}
/**
* {@inheritDoc}
*/
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);
}
/**
* {@inheritDoc}
*/
public boolean isAlive() {
return true;
}
}
}
/**
* A form of self-injection where auxiliary types that are annotated by
* {@link net.bytebuddy.implementation.auxiliary.AuxiliaryType.SignatureRelevant} of the instrumented type are loaded lazily and
* any other auxiliary type is loaded eagerly.
*/
public static class Split extends SelfInjection {
/**
* Creates a new split self-injection strategy that uses a default nexus accessor.
*/
public Split() {
this(new NexusAccessor());
}
/**
* Creates a new split self-injection strategy that uses the supplied nexus accessor.
*
* @param nexusAccessor The nexus accessor to use.
*/
public Split(NexusAccessor nexusAccessor) {
super(nexusAccessor);
}
@Override
protected InitializationStrategy.Dispatcher dispatcher(int identification) {
return new Dispatcher(nexusAccessor, identification);
}
/**
* A dispatcher for the {@link net.bytebuddy.agent.builder.AgentBuilder.InitializationStrategy.SelfInjection.Split} strategy.
*/
protected static class Dispatcher extends SelfInjection.Dispatcher {
/**
* Creates a new split dispatcher.
*
* @param nexusAccessor The nexus accessor to use.
* @param identification A random identification for the applied self-initialization.
*/
protected Dispatcher(NexusAccessor nexusAccessor, int identification) {
super(nexusAccessor, identification);
}
/**
* {@inheritDoc}
*/
public void register(DynamicType dynamicType,
@MaybeNull ClassLoader classLoader,
@MaybeNull ProtectionDomain protectionDomain,
InjectionStrategy injectionStrategy) {
Map auxiliaryTypes = dynamicType.getAuxiliaryTypes();
LoadedTypeInitializer loadedTypeInitializer;
if (!auxiliaryTypes.isEmpty()) {
TypeDescription instrumentedType = dynamicType.getTypeDescription();
ClassInjector classInjector = injectionStrategy.resolve(classLoader, protectionDomain);
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 Dispatcher.InjectingInitializer(instrumentedType, dependentTypes, lazyInitializers, classInjector)
: lazyInitializers.get(instrumentedType);
} else {
loadedTypeInitializer = dynamicType.getLoadedTypeInitializers().get(dynamicType.getTypeDescription());
}
nexusAccessor.register(dynamicType.getTypeDescription().getName(), classLoader, identification, loadedTypeInitializer);
}
}
}
/**
* A form of self-injection where any auxiliary type is loaded lazily.
*/
public static class Lazy extends SelfInjection {
/**
* Creates a new lazy self-injection strategy that uses a default nexus accessor.
*/
public Lazy() {
this(new NexusAccessor());
}
/**
* Creates a new lazy self-injection strategy that uses the supplied nexus accessor.
*
* @param nexusAccessor The nexus accessor to use.
*/
public Lazy(NexusAccessor nexusAccessor) {
super(nexusAccessor);
}
@Override
protected InitializationStrategy.Dispatcher dispatcher(int identification) {
return new Dispatcher(nexusAccessor, identification);
}
/**
* A dispatcher for the {@link net.bytebuddy.agent.builder.AgentBuilder.InitializationStrategy.SelfInjection.Lazy} strategy.
*/
protected static class Dispatcher extends SelfInjection.Dispatcher {
/**
* Creates a new lazy dispatcher.
*
* @param nexusAccessor The nexus accessor to use.
* @param identification A random identification for the applied self-initialization.
*/
protected Dispatcher(NexusAccessor nexusAccessor, int identification) {
super(nexusAccessor, identification);
}
/**
* {@inheritDoc}
*/
public void register(DynamicType dynamicType,
@MaybeNull ClassLoader classLoader,
@MaybeNull ProtectionDomain protectionDomain,
InjectionStrategy injectionStrategy) {
Map auxiliaryTypes = dynamicType.getAuxiliaryTypes();
LoadedTypeInitializer loadedTypeInitializer = auxiliaryTypes.isEmpty()
? dynamicType.getLoadedTypeInitializers().get(dynamicType.getTypeDescription())
: new Dispatcher.InjectingInitializer(dynamicType.getTypeDescription(), auxiliaryTypes, dynamicType.getLoadedTypeInitializers(), injectionStrategy.resolve(classLoader, protectionDomain));
nexusAccessor.register(dynamicType.getTypeDescription().getName(), classLoader, identification, loadedTypeInitializer);
}
}
}
/**
* A form of self-injection where any auxiliary type is loaded eagerly.
*/
public static class Eager extends SelfInjection {
/**
* Creates a new eager self-injection strategy that uses a default nexus accessor.
*/
public Eager() {
this(new NexusAccessor());
}
/**
* Creates a new eager self-injection strategy that uses the supplied nexus accessor.
*
* @param nexusAccessor The nexus accessor to use.
*/
public Eager(NexusAccessor nexusAccessor) {
super(nexusAccessor);
}
@Override
protected InitializationStrategy.Dispatcher dispatcher(int identification) {
return new Dispatcher(nexusAccessor, identification);
}
/**
* A dispatcher for the {@link net.bytebuddy.agent.builder.AgentBuilder.InitializationStrategy.SelfInjection.Eager} strategy.
*/
protected static class Dispatcher extends SelfInjection.Dispatcher {
/**
* Creates a new eager dispatcher.
*
* @param nexusAccessor The nexus accessor to use.
* @param identification A random identification for the applied self-initialization.
*/
protected Dispatcher(NexusAccessor nexusAccessor, int identification) {
super(nexusAccessor, identification);
}
/**
* {@inheritDoc}
*/
public void register(DynamicType dynamicType,
@MaybeNull ClassLoader classLoader,
@MaybeNull ProtectionDomain protectionDomain,
InjectionStrategy injectionStrategy) {
Map auxiliaryTypes = dynamicType.getAuxiliaryTypes();
Map loadedTypeInitializers = dynamicType.getLoadedTypeInitializers();
if (!auxiliaryTypes.isEmpty()) {
for (Map.Entry> entry : injectionStrategy.resolve(classLoader, protectionDomain).inject(auxiliaryTypes).entrySet()) {
loadedTypeInitializers.get(entry.getKey()).onLoad(entry.getValue());
}
}
LoadedTypeInitializer loadedTypeInitializer = loadedTypeInitializers.get(dynamicType.getTypeDescription());
nexusAccessor.register(dynamicType.getTypeDescription().getName(), classLoader, identification, loadedTypeInitializer);
}
}
}
}
}
/**
* A strategy for injecting auxiliary types into a class loader.
*/
interface InjectionStrategy {
/**
* Resolves the class injector to use for a given class loader and protection domain.
*
* @param classLoader The class loader to use or {@code null} if using the bootstrap loader.
* @param protectionDomain The protection domain to use or {@code null} if all privileges should be assigned.
* @return The class injector to use.
*/
ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain);
/**
* An injection strategy that does not permit class injection.
*/
enum Disabled implements InjectionStrategy {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain) {
throw new IllegalStateException("Class injection is disabled");
}
}
/**
* An injection strategy that uses Java reflection. This strategy is not capable of injecting classes into the bootstrap class loader.
*/
enum UsingReflection implements InjectionStrategy {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain) {
if (classLoader == null) {
throw new IllegalStateException("Cannot inject auxiliary class into bootstrap loader using reflection");
} else if (ClassInjector.UsingReflection.isAvailable()) {
return new ClassInjector.UsingReflection(classLoader, protectionDomain);
} else {
throw new IllegalStateException("Reflection-based injection is not available on the current VM");
}
}
}
/**
* An injection strategy that uses {@code sun.misc.Unsafe} or {@code jdk.internal.misc.Unsafe} to inject classes.
*/
enum UsingUnsafe implements InjectionStrategy {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain) {
if (ClassInjector.UsingUnsafe.isAvailable()) {
return new ClassInjector.UsingUnsafe(classLoader, protectionDomain);
} else {
throw new IllegalStateException("Unsafe-based injection is not available on the current VM");
}
}
/**
* An injection strategy that uses a factory for creating an unsafe injector.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class OfFactory implements InjectionStrategy {
/**
* The factory to use for creating an unsafe injector.
*/
private final ClassInjector.UsingUnsafe.Factory factory;
/**
* Creates an injection strategy based on a factory.
*
* @param factory The factory to use for creating an unsafe injector.
*/
public OfFactory(ClassInjector.UsingUnsafe.Factory factory) {
this.factory = factory;
}
/**
* {@inheritDoc}
*/
public ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain) {
return factory.make(classLoader, protectionDomain);
}
}
}
/**
* An injection strategy that uses JNA to inject classes. This strategy is only available if JNA was added as a dependency.
*/
enum UsingJna implements InjectionStrategy {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain) {
if (ClassInjector.UsingJna.isAvailable()) {
return new ClassInjector.UsingJna(classLoader, protectionDomain);
} else {
throw new IllegalStateException("JNA-based injection is not available on the current VM");
}
}
}
/**
* An injection strategy that uses bootstrap injection using an {@link Instrumentation} instance.
*/
@HashCodeAndEqualsPlugin.Enhance
class UsingInstrumentation implements InjectionStrategy {
/**
* The instrumentation instance to use.
*/
private final Instrumentation instrumentation;
/**
* The folder to store jar files being used for bootstrap injection.
*/
private final File folder;
/**
* Creates a new bootstrap injection strategy.
*
* @param instrumentation The instrumentation instance to use.
* @param folder The folder to store jar files being used for bootstrap injection.
*/
public UsingInstrumentation(Instrumentation instrumentation, File folder) {
this.instrumentation = instrumentation;
this.folder = folder;
}
/**
* {@inheritDoc}
*/
public ClassInjector resolve(@MaybeNull ClassLoader classLoader, @MaybeNull ProtectionDomain protectionDomain) {
return classLoader == null
? ClassInjector.UsingInstrumentation.of(folder, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, instrumentation)
: UsingReflection.INSTANCE.resolve(classLoader, protectionDomain);
}
}
}
/**
* A description strategy is responsible for resolving a {@link TypeDescription} when transforming or retransforming/-defining a type.
*/
interface DescriptionStrategy {
/**
* Indicates if this description strategy makes use of loaded type information and yields a different type description if no loaded type is available.
*
* @return {@code true} if this description strategy prefers loaded type information when describing a type and only uses a type pool
* if loaded type information is not available.
*/
boolean isLoadedFirst();
/**
* Describes the given type.
*
* @param name The binary name of the type to describe.
* @param type The type that is being redefined, if a redefinition is applied or {@code null} if no redefined type is available.
* @param typePool The type pool to use for locating a type if required.
* @param classLoader The type's class loader where {@code null} represents the bootstrap class loader.
* @param circularityLock The currently used circularity lock.
* @param module The type's module or {@code null} if the current VM does not support modules.
* @return An appropriate type description.
*/
TypeDescription apply(String name, @MaybeNull Class> type, TypePool typePool, CircularityLock circularityLock, @MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module);
/**
* Default implementations of a {@link DescriptionStrategy}.
*/
enum Default implements DescriptionStrategy {
/**
* A description type strategy represents a type as a {@link net.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.
*
* @see FallbackStrategy.Simple#ENABLED
* @see FallbackStrategy.ByThrowableType#ofOptionalTypes()
*/
HYBRID(true) {
/** {@inheritDoc} */
public TypeDescription apply(String name,
@MaybeNull Class> type,
TypePool typePool,
CircularityLock circularityLock,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module) {
return type == null
? typePool.describe(name).resolve()
: TypeDescription.ForLoadedType.of(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(false) {
/** {@inheritDoc} */
public TypeDescription apply(String name,
@MaybeNull Class> type,
TypePool typePool,
CircularityLock circularityLock,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module) {
return typePool.describe(name).resolve();
}
},
/**
*
* A description strategy that always describes Java types using a {@link TypePool} unless a type cannot be resolved by a pool and a loaded
* {@link Class} instance is available. 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_FIRST(false) {
/** {@inheritDoc} */
public TypeDescription apply(String name,
@MaybeNull Class> type,
TypePool typePool,
CircularityLock circularityLock,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module) {
TypePool.Resolution resolution = typePool.describe(name);
return resolution.isResolved() || type == null
? resolution.resolve()
: TypeDescription.ForLoadedType.of(type);
}
};
/**
* Indicates if loaded type information is preferred over using a type pool for describing a type.
*/
private final boolean loadedFirst;
/**
* Indicates if loaded type information is preferred over using a type pool for describing a type.
*
* @param loadedFirst {@code true} if loaded type information is preferred over using a type pool for describing a type.
*/
Default(boolean loadedFirst) {
this.loadedFirst = loadedFirst;
}
/**
* Creates a description strategy that uses this strategy but loads any super type. If a super type is not yet loaded,
* this causes this super type to never be instrumented. Therefore, this option should only be used if all instrumented
* types are guaranteed to be top-level types.
*
* @return This description strategy where all super types are loaded during the instrumentation.
* @see SuperTypeLoading
*/
public DescriptionStrategy withSuperTypeLoading() {
return new SuperTypeLoading(this);
}
/**
* {@inheritDoc}
*/
public boolean isLoadedFirst() {
return loadedFirst;
}
/**
* Creates a description strategy that uses this strategy but loads any super type asynchronously. Super types are loaded via
* another thread supplied by the executor service to enforce the instrumentation of any such super type. It is recommended
* to allow the executor service to create new threads without bound as class loading blocks any thread until all super types
* were instrumented.
*
* @param executorService The executor service to use.
* @return This description strategy where all super types are loaded asynchronously during the instrumentation.
* @see SuperTypeLoading.Asynchronous
*/
public DescriptionStrategy withSuperTypeLoading(ExecutorService executorService) {
return new SuperTypeLoading.Asynchronous(this, executorService);
}
}
/**
*
* A description strategy that enforces the loading of any super type of a type description but delegates the actual type description
* to another description strategy.
*
*
* Warning: When using this description strategy, a type is not instrumented if any of its subtypes is loaded first.
* The instrumentation API does not submit such types to a class file transformer on most VM implementations.
*
*/
@HashCodeAndEqualsPlugin.Enhance
class SuperTypeLoading implements DescriptionStrategy {
/**
* The delegate description strategy.
*/
private final DescriptionStrategy delegate;
/**
* Creates a new description strategy that enforces loading of a super type.
*
* @param delegate The delegate description strategy.
*/
public SuperTypeLoading(DescriptionStrategy delegate) {
this.delegate = delegate;
}
/**
* {@inheritDoc}
*/
public boolean isLoadedFirst() {
return delegate.isLoadedFirst();
}
/**
* {@inheritDoc}
*/
public TypeDescription apply(String name,
@MaybeNull Class> type,
TypePool typePool,
CircularityLock circularityLock,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module) {
TypeDescription typeDescription = delegate.apply(name, type, typePool, circularityLock, classLoader, module);
return typeDescription instanceof TypeDescription.ForLoadedType
? typeDescription
: new TypeDescription.SuperTypeLoading(typeDescription, classLoader, new UnlockingClassLoadingDelegate(circularityLock));
}
/**
* A class loading delegate that unlocks the circularity lock during class loading.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class UnlockingClassLoadingDelegate implements TypeDescription.SuperTypeLoading.ClassLoadingDelegate {
/**
* The circularity lock to unlock.
*/
private final CircularityLock circularityLock;
/**
* Creates an unlocking class loading delegate.
*
* @param circularityLock The circularity lock to unlock.
*/
protected UnlockingClassLoadingDelegate(CircularityLock circularityLock) {
this.circularityLock = circularityLock;
}
/**
* {@inheritDoc}
*/
public Class> load(String name, @MaybeNull ClassLoader classLoader) throws ClassNotFoundException {
circularityLock.release();
try {
return Class.forName(name, false, classLoader);
} finally {
circularityLock.acquire();
}
}
}
/**
*
* A description strategy that enforces the loading of any super type of a type description but delegates the actual type description
* to another description strategy.
*
*
* Note: This description strategy delegates class loading to another thread in order to enforce the instrumentation of any
* unloaded super type. This requires the executor service to supply at least as many threads as the deepest type hierarchy within the
* application minus one for the instrumented type as class loading blocks any thread until all of its super types are loaded. These
* threads are typically short lived which predestines the use of a {@link Executors#newCachedThreadPool()} without any upper bound
* for the maximum number of created threads.
*
*
* Important: This strategy can dead-lock under two circumstances:
*
*
* -
* Classes declare circularities: Under normal circumstances, such scenarios result in a {@link ClassCircularityError} but
* can result in dead-locks when using this instrumentation strategy.
*
* -
* Class loaders declare custom locks: If a class loader locks another lock but itself during class loading, this lock cannot
* be released by this strategy.
*
*
*
* For the above reasons, it is not recommended to use this strategy when the target class loader is unknown or if the target application
* might contain corrupt class files.
*
*/
@HashCodeAndEqualsPlugin.Enhance
public static class Asynchronous implements DescriptionStrategy {
/**
* The delegate description strategy.
*/
private final DescriptionStrategy delegate;
/**
* The executor service to use for loading super types.
*/
private final ExecutorService executorService;
/**
* Creates a new description strategy that enforces super type loading from another thread.
*
* @param delegate The delegate description strategy.
* @param executorService The executor service to use for loading super types.
*/
public Asynchronous(DescriptionStrategy delegate, ExecutorService executorService) {
this.delegate = delegate;
this.executorService = executorService;
}
/**
* {@inheritDoc}
*/
public boolean isLoadedFirst() {
return delegate.isLoadedFirst();
}
/**
* {@inheritDoc}
*/
public TypeDescription apply(String name,
@MaybeNull Class> type,
TypePool typePool,
CircularityLock circularityLock,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module) {
TypeDescription typeDescription = delegate.apply(name, type, typePool, circularityLock, classLoader, module);
return typeDescription instanceof TypeDescription.ForLoadedType
? typeDescription
: new TypeDescription.SuperTypeLoading(typeDescription, classLoader, new ThreadSwitchingClassLoadingDelegate(executorService));
}
/**
* A class loading delegate that delegates loading of the super type to another thread.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class ThreadSwitchingClassLoadingDelegate implements TypeDescription.SuperTypeLoading.ClassLoadingDelegate {
/**
* The executor service to delegate class loading to.
*/
private final ExecutorService executorService;
/**
* Creates a new thread-switching class loading delegate.
*
* @param executorService The executor service to delegate class loading to.
*/
protected ThreadSwitchingClassLoadingDelegate(ExecutorService executorService) {
this.executorService = executorService;
}
/**
* {@inheritDoc}
*/
public Class> load(String name, @MaybeNull ClassLoader classLoader) {
boolean holdsLock = classLoader != null && Thread.holdsLock(classLoader);
AtomicBoolean signal = new AtomicBoolean(holdsLock);
Future> future = executorService.submit(holdsLock
? new NotifyingClassLoadingAction(name, classLoader, signal)
: new SimpleClassLoadingAction(name, classLoader));
try {
while (holdsLock && signal.get()) {
classLoader.wait();
}
return future.get();
} catch (ExecutionException exception) {
throw new IllegalStateException("Could not load " + name + " asynchronously", exception.getCause());
} catch (Exception exception) {
throw new IllegalStateException("Could not load " + name + " asynchronously", exception);
}
}
/**
* A class loading action that simply loads a type.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class SimpleClassLoadingAction implements Callable> {
/**
* The loaded type's name.
*/
private final String name;
/**
* The type's class loader or {@code null} if the type is loaded by the bootstrap loader.
*/
@MaybeNull
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
private final ClassLoader classLoader;
/**
* Creates a simple class loading action.
*
* @param name The loaded type's name.
* @param classLoader The type's class loader or {@code null} if the type is loaded by the bootstrap loader.
*/
protected SimpleClassLoadingAction(String name, @MaybeNull ClassLoader classLoader) {
this.name = name;
this.classLoader = classLoader;
}
/**
* {@inheritDoc}
*/
public Class> call() throws ClassNotFoundException {
return Class.forName(name, false, classLoader);
}
}
/**
* A class loading action that notifies the class loader's lock after the type was loaded.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class NotifyingClassLoadingAction implements Callable> {
/**
* The loaded type's name.
*/
private final String name;
/**
* The type's class loader which must not be the boot loader, i.e {@code null}.
*/
private final ClassLoader classLoader;
/**
* The signal that indicates the completion of the class loading with {@code false}.
*/
private final AtomicBoolean signal;
/**
* Creates a notifying class loading action.
*
* @param name The loaded type's name.
* @param classLoader The type's class loader which must not be the boot loader, i.e {@code null}.
* @param signal The signal that indicates the completion of the class loading with {@code false}.
*/
protected NotifyingClassLoadingAction(String name, ClassLoader classLoader, AtomicBoolean signal) {
this.name = name;
this.classLoader = classLoader;
this.signal = signal;
}
/**
* {@inheritDoc}
*/
public Class> call() throws ClassNotFoundException {
synchronized (classLoader) {
try {
return Class.forName(name, false, classLoader);
} finally {
signal.set(false);
classLoader.notifyAll();
}
}
}
}
}
}
}
}
/**
* A strategy for creating a {@link ClassFileLocator} when instrumenting a type.
*/
interface LocationStrategy {
/**
* Creates a class file locator for a given class loader and module combination.
*
* @param classLoader The class loader that is loading an instrumented type. Might be {@code null} to represent the bootstrap class loader.
* @param module The type's module or {@code null} if Java modules are not supported on the current VM.
* @return The class file locator to use.
*/
ClassFileLocator classFileLocator(@MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module);
/**
* A location strategy that never locates any byte code.
*/
enum NoOp implements LocationStrategy {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public ClassFileLocator classFileLocator(@MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
return ClassFileLocator.NoOp.INSTANCE;
}
}
/**
* A location strategy that locates class files by querying an instrumented type's {@link ClassLoader}.
*/
enum ForClassLoader implements LocationStrategy {
/**
* A location strategy that keeps a strong reference to the class loader the created class file locator represents.
*/
STRONG {
/** {@inheritDoc} */
public ClassFileLocator classFileLocator(@MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
return ClassFileLocator.ForClassLoader.of(classLoader);
}
},
/**
* A location strategy that keeps a weak reference to the class loader the created class file locator represents.
* As a consequence, any returned class file locator stops working once the represented class loader is garbage collected.
*/
WEAK {
/** {@inheritDoc} */
public ClassFileLocator classFileLocator(@MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
return ClassFileLocator.ForClassLoader.WeaklyReferenced.of(classLoader);
}
};
/**
* Adds additional location strategies as fallbacks to this location strategy.
*
* @param classFileLocator The class file locators to query if this location strategy cannot locate a class file.
* @return A compound location strategy that first applies this location strategy and then queries the supplied class file locators.
*/
public LocationStrategy withFallbackTo(ClassFileLocator... classFileLocator) {
return withFallbackTo(Arrays.asList(classFileLocator));
}
/**
* Adds additional location strategies as fallbacks to this location strategy.
*
* @param classFileLocators The class file locators to query if this location strategy cannot locate a class file.
* @return A compound location strategy that first applies this location strategy and then queries the supplied class file locators.
*/
public LocationStrategy withFallbackTo(Collection extends ClassFileLocator> classFileLocators) {
List locationStrategies = new ArrayList(classFileLocators.size());
for (ClassFileLocator classFileLocator : classFileLocators) {
locationStrategies.add(new Simple(classFileLocator));
}
return withFallbackTo(locationStrategies);
}
/**
* Adds additional location strategies as fallbacks to this location strategy.
*
* @param locationStrategy The fallback location strategies to use.
* @return A compound location strategy that first applies this location strategy and then the supplied fallback location strategies
* in the supplied order.
*/
public LocationStrategy withFallbackTo(LocationStrategy... locationStrategy) {
return withFallbackTo(Arrays.asList(locationStrategy));
}
/**
* Adds additional location strategies as fallbacks to this location strategy.
*
* @param locationStrategies The fallback location strategies to use.
* @return A compound location strategy that first applies this location strategy and then the supplied fallback location strategies
* in the supplied order.
*/
public LocationStrategy withFallbackTo(List extends LocationStrategy> locationStrategies) {
List allLocationStrategies = new ArrayList(locationStrategies.size() + 1);
allLocationStrategies.add(this);
allLocationStrategies.addAll(locationStrategies);
return new Compound(allLocationStrategies);
}
}
/**
* A simple location strategy that queries a given class file locator.
*/
@HashCodeAndEqualsPlugin.Enhance
class Simple implements LocationStrategy {
/**
* The class file locator to query.
*/
private final ClassFileLocator classFileLocator;
/**
* A simple location strategy that queries a given class file locator.
*
* @param classFileLocator The class file locator to query.
*/
public Simple(ClassFileLocator classFileLocator) {
this.classFileLocator = classFileLocator;
}
/**
* {@inheritDoc}
*/
public ClassFileLocator classFileLocator(@MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
return classFileLocator;
}
}
/**
* A compound location strategy that applies a list of location strategies.
*/
@HashCodeAndEqualsPlugin.Enhance
class Compound implements LocationStrategy {
/**
* The location strategies in their application order.
*/
private final List locationStrategies;
/**
* Creates a new compound location strategy.
*
* @param locationStrategy The location strategies in their application order.
*/
public Compound(LocationStrategy... locationStrategy) {
this(Arrays.asList(locationStrategy));
}
/**
* Creates a new compound location strategy.
*
* @param locationStrategies The location strategies in their application order.
*/
public Compound(List extends LocationStrategy> locationStrategies) {
this.locationStrategies = new ArrayList();
for (LocationStrategy locationStrategy : locationStrategies) {
if (locationStrategy instanceof Compound) {
this.locationStrategies.addAll(((Compound) locationStrategy).locationStrategies);
} else if (!(locationStrategy instanceof NoOp)) {
this.locationStrategies.add(locationStrategy);
}
}
}
/**
* {@inheritDoc}
*/
public ClassFileLocator classFileLocator(@MaybeNull ClassLoader classLoader, @MaybeNull JavaModule module) {
List classFileLocators = new ArrayList(locationStrategies.size());
for (LocationStrategy locationStrategy : locationStrategies) {
classFileLocators.add(locationStrategy.classFileLocator(classLoader, module));
}
return new ClassFileLocator.Compound(classFileLocators);
}
}
}
/**
* A fallback strategy allows to reattempt a transformation or a consideration for redefinition/retransformation in case an exception
* occurs. Doing so, it is possible to use a {@link TypePool} rather than using a loaded type description backed by a {@link Class}.
* Loaded types can raise exceptions and errors if a {@link ClassLoader} cannot resolve all types that this class references. Using
* a type pool, such errors can be avoided as type descriptions can be resolved lazily, avoiding such errors.
*/
interface FallbackStrategy {
/**
* Returns {@code true} if the supplied type and throwable combination should result in a reattempt where the
* loaded type is not used for querying information.
*
* @param type The loaded type that was queried during the transformation attempt.
* @param throwable The error or exception that was caused during the transformation.
* @return {@code true} if the supplied type and throwable combination should
*/
boolean isFallback(Class> type, Throwable throwable);
/**
* A simple fallback strategy that either always reattempts a transformation or never does so.
*/
enum Simple implements FallbackStrategy {
/**
* An enabled fallback strategy that always attempts a new trial.
*/
ENABLED(true),
/**
* A disabled fallback strategy that never attempts a new trial.
*/
DISABLED(false);
/**
* {@code true} if this fallback strategy is enabled.
*/
private final boolean enabled;
/**
* Creates a new default fallback strategy.
*
* @param enabled {@code true} if this fallback strategy is enabled.
*/
Simple(boolean enabled) {
this.enabled = enabled;
}
/**
* {@inheritDoc}
*/
public boolean isFallback(Class> type, Throwable throwable) {
return enabled;
}
}
/**
* A fallback strategy that discriminates by the type of the {@link Throwable} that triggered a request.
*/
@HashCodeAndEqualsPlugin.Enhance
class ByThrowableType implements FallbackStrategy {
/**
* A set of throwable types that should trigger a fallback attempt.
*/
private final Set extends Class extends Throwable>> types;
/**
* Creates a new throwable type-discriminating fallback strategy.
*
* @param type The throwable types that should trigger a fallback.
*/
@SuppressWarnings("unchecked") // In absence of @SafeVarargs
public ByThrowableType(Class extends Throwable>... type) {
this(new HashSet>(Arrays.asList(type)));
}
/**
* Creates a new throwable type-discriminating fallback strategy.
*
* @param types The throwable types that should trigger a fallback.
*/
public ByThrowableType(Set extends Class extends Throwable>> types) {
this.types = types;
}
/**
* Creates a fallback strategy that attempts a fallback if an error indicating a type error is the reason for requesting a reattempt.
*
* @return A fallback strategy that triggers a reattempt if a {@link LinkageError} or a {@link TypeNotPresentException} is raised.
*/
@SuppressWarnings("unchecked") // In absence of @SafeVarargs
public static FallbackStrategy ofOptionalTypes() {
return new ByThrowableType(LinkageError.class, TypeNotPresentException.class);
}
/**
* {@inheritDoc}
*/
public boolean isFallback(Class> type, Throwable throwable) {
for (Class extends Throwable> aType : types) {
if (aType.isInstance(throwable)) {
return true;
}
}
return false;
}
}
}
/**
* A listener that is notified during the installation and the resetting of a class file transformer.
*/
interface InstallationListener {
/**
* Indicates that an exception is handled.
*/
@AlwaysNull
Throwable SUPPRESS_ERROR = null;
/**
* Invoked prior to the installation of a class file transformer.
*
* @param instrumentation The instrumentation on which the class file transformer is installed.
* @param classFileTransformer The class file transformer that is being installed.
*/
void onBeforeInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer);
/**
* Invoked upon the successful installation of a class file transformer. This method is only invoked if no error occurred during the
* installation or if such an error was handled by {@link InstallationListener#onError(Instrumentation, ResettableClassFileTransformer, Throwable)}.
*
* @param instrumentation The instrumentation on which the class file transformer is installed.
* @param classFileTransformer The class file transformer that is being installed.
*/
void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer);
/**
* Invoked if an installation causes an error. The listener has an opportunity to handle the error. This method is invoked prior to
* {@link InstallationListener#onInstall(Instrumentation, ResettableClassFileTransformer)}.
*
* @param instrumentation The instrumentation on which the class file transformer is installed.
* @param classFileTransformer The class file transformer that is being installed.
* @param throwable The throwable that causes the error.
* @return The error to propagate or {@code null} if the error is handled. Any subsequent listeners are not called if the exception is handled.
*/
@MaybeNull
Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable);
/**
* Invoked if an installation is reset.
*
* @param instrumentation The instrumentation on which the class file transformer is installed.
* @param classFileTransformer The class file transformer that is being installed.
*/
void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer);
/**
* Invoked before a warump is executed.
*
* @param types The types that are used for the warmup.
* @param classFileTransformer The class file transformer that is warmed up.
*/
void onBeforeWarmUp(Set> types, ResettableClassFileTransformer classFileTransformer);
/**
* Invoked when a class yields an unexpected error that is not catched by the listener.
*
* @param type The type that caused the error.
* @param classFileTransformer The class file transformer that is warmed up.
* @param throwable The throwable that represents the error.
*/
void onWarmUpError(Class> type, ResettableClassFileTransformer classFileTransformer, Throwable throwable);
/**
* Invoked after a warump is executed.
*
* @param types The types that are used for the warmup mapped to their transformed byte code
* or {@code null} if the type was not transformed or failed to transform.
* @param classFileTransformer The class file transformer that is warmed up.
* @param transformed {@code true} if at least one class caused an actual transformation.
*/
void onAfterWarmUp(Map, byte[]> types, ResettableClassFileTransformer classFileTransformer, boolean transformed);
/**
* A non-operational listener that does not do anything.
*/
enum NoOp implements InstallationListener {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public void onBeforeInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
return throwable;
}
/**
* {@inheritDoc}
*/
public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onBeforeWarmUp(Set> types, ResettableClassFileTransformer classFileTransformer) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onWarmUpError(Class> type, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onAfterWarmUp(Map, byte[]> types, ResettableClassFileTransformer classFileTransformer, boolean transformed) {
/* do nothing */
}
}
/**
* A listener that suppresses any installation error.
*/
enum ErrorSuppressing implements InstallationListener {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public void onBeforeInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
@MaybeNull
public Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
return SUPPRESS_ERROR;
}
/**
* {@inheritDoc}
*/
public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onBeforeWarmUp(Set> types, ResettableClassFileTransformer classFileTransformer) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onWarmUpError(Class> type, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onAfterWarmUp(Map, byte[]> types, ResettableClassFileTransformer classFileTransformer, boolean transformed) {
/* do nothing */
}
}
/**
* An adapter implementation for an installation listener that serves as a convenience.
*/
abstract class Adapter implements InstallationListener {
/**
* {@inheritDoc}
*/
public void onBeforeInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
return throwable;
}
/**
* {@inheritDoc}
*/
public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onBeforeWarmUp(Set> types, ResettableClassFileTransformer classFileTransformer) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onWarmUpError(Class> type, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public void onAfterWarmUp(Map, byte[]> types, ResettableClassFileTransformer classFileTransformer, boolean transformed) {
/* do nothing */
}
}
/**
* This installation listener prints the status of any installation to a {@link PrintStream}.
*/
@HashCodeAndEqualsPlugin.Enhance
class StreamWriting implements InstallationListener {
/**
* The prefix prepended to any message written.
*/
protected static final String PREFIX = "[Byte Buddy]";
/**
* The print stream to write to.
*/
private final PrintStream printStream;
/**
* Creates a new stream writing installation listener.
*
* @param printStream The print stream to write to.
*/
public StreamWriting(PrintStream printStream) {
this.printStream = printStream;
}
/**
* Creates a stream writing installation listener that prints to {@link System#out}.
*
* @return An installation listener that prints to {@link System#out}.
*/
public static InstallationListener toSystemOut() {
return new StreamWriting(System.out);
}
/**
* Creates a stream writing installation listener that prints to {@link System#err}.
*
* @return An installation listener that prints to {@link System#err}.
*/
public static InstallationListener toSystemError() {
return new StreamWriting(System.err);
}
/**
* {@inheritDoc}
*/
public void onBeforeInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
printStream.printf(PREFIX + " BEFORE_INSTALL %s on %s%n", classFileTransformer, instrumentation);
}
/**
* {@inheritDoc}
*/
public void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
printStream.printf(PREFIX + " INSTALL %s on %s%n", classFileTransformer, instrumentation);
}
/**
* {@inheritDoc}
*/
public Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
synchronized (printStream) {
printStream.printf(PREFIX + " ERROR %s on %s%n", classFileTransformer, instrumentation);
throwable.printStackTrace(printStream);
}
return throwable;
}
/**
* {@inheritDoc}
*/
public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
printStream.printf(PREFIX + " RESET %s on %s%n", classFileTransformer, instrumentation);
}
/**
* {@inheritDoc}
*/
public void onBeforeWarmUp(Set> types, ResettableClassFileTransformer classFileTransformer) {
printStream.printf(PREFIX + " BEFORE_WARMUP %s on %s%n", classFileTransformer, types);
}
/**
* {@inheritDoc}
*/
public void onWarmUpError(Class> type, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
synchronized (printStream) {
printStream.printf(PREFIX + " ERROR_WARMUP %s on %s%n", classFileTransformer, type);
throwable.printStackTrace(printStream);
}
}
/**
* {@inheritDoc}
*/
public void onAfterWarmUp(Map, byte[]> types, ResettableClassFileTransformer classFileTransformer, boolean transformed) {
printStream.printf(PREFIX + " AFTER_WARMUP %s %s on %s%n", transformed ? "transformed" : "not transformed", classFileTransformer, types.keySet());
}
}
/**
* A compound installation listener.
*/
@HashCodeAndEqualsPlugin.Enhance
class Compound implements InstallationListener {
/**
* The installation listeners to notify.
*/
private final List installationListeners;
/**
* Creates a new compound listener.
*
* @param installationListener The installation listeners to notify.
*/
public Compound(InstallationListener... installationListener) {
this(Arrays.asList(installationListener));
}
/**
* Creates a new compound listener.
*
* @param installationListeners The installation listeners to notify.
*/
public Compound(List extends InstallationListener> installationListeners) {
this.installationListeners = new ArrayList();
for (InstallationListener installationListener : installationListeners) {
if (installationListener instanceof Compound) {
this.installationListeners.addAll(((Compound) installationListener).installationListeners);
} else if (!(installationListener instanceof NoOp)) {
this.installationListeners.add(installationListener);
}
}
}
/**
* {@inheritDoc}
*/
public void onBeforeInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
for (InstallationListener installationListener : installationListeners) {
installationListener.onBeforeInstall(instrumentation, classFileTransformer);
}
}
/**
* {@inheritDoc}
*/
public void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
for (InstallationListener installationListener : installationListeners) {
installationListener.onInstall(instrumentation, classFileTransformer);
}
}
/**
* {@inheritDoc}
*/
@MaybeNull
public Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
for (InstallationListener installationListener : installationListeners) {
if (throwable == SUPPRESS_ERROR) {
return SUPPRESS_ERROR;
}
throwable = installationListener.onError(instrumentation, classFileTransformer, throwable);
}
return throwable;
}
/**
* {@inheritDoc}
*/
public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
for (InstallationListener installationListener : installationListeners) {
installationListener.onReset(instrumentation, classFileTransformer);
}
}
/**
* {@inheritDoc}
*/
public void onBeforeWarmUp(Set> types, ResettableClassFileTransformer classFileTransformer) {
for (InstallationListener installationListener : installationListeners) {
installationListener.onBeforeWarmUp(types, classFileTransformer);
}
}
/**
* {@inheritDoc}
*/
public void onWarmUpError(Class> type, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
for (InstallationListener installationListener : installationListeners) {
installationListener.onWarmUpError(type, classFileTransformer, throwable);
}
}
/**
* {@inheritDoc}
*/
public void onAfterWarmUp(Map, byte[]> types, ResettableClassFileTransformer classFileTransformer, boolean transformed) {
for (InstallationListener installationListener : installationListeners) {
installationListener.onAfterWarmUp(types, classFileTransformer, transformed);
}
}
}
}
/**
* This strategy determines how the provided class file buffer is used.
*/
interface ClassFileBufferStrategy {
/**
* Resolves a class file locator for the class file buffer that is provided to the class file transformer.
*
* @param name The instrumented type's binary name.
* @param binaryRepresentation The instrumented type's binary representation.
* @param classLoader The instrumented type's class loader or {@code null} if the type is loaded by the bootstrap class loader.
* @param module The instrumented type's module or {@code null} if the current VM does not support modules.
* @param protectionDomain The instrumented type's protection domain or {@code null} if not available
* @return An appropriate class file locator.
*/
ClassFileLocator resolve(String name,
byte[] binaryRepresentation,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain);
/**
* Resolves the type pool for a given type name by the supplied {@link PoolStrategy}.
*
* @param poolStrategy The pool strategy to use.
* @param classFileLocator The class file locator to use.
* @param classLoader The class loader to use.
* @param name The name of the type for which the type pool is resolved.
* @return A suitable type pool.
*/
TypePool typePool(PoolStrategy poolStrategy, ClassFileLocator classFileLocator, @MaybeNull ClassLoader classLoader, String name);
/**
* An implementation of default class file buffer strategy.
*/
enum Default implements ClassFileBufferStrategy {
/**
* A class file buffer strategy that retains the original class file buffer.
*/
RETAINING {
/** {@inheritDoc} */
public ClassFileLocator resolve(String name,
byte[] binaryRepresentation,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain) {
return ClassFileLocator.Simple.of(name, binaryRepresentation);
}
/** {@inheritDoc} */
public TypePool typePool(PoolStrategy poolStrategy,
ClassFileLocator classFileLocator,
@MaybeNull ClassLoader classLoader,
String name) {
return poolStrategy.typePool(classFileLocator, classLoader, name);
}
},
/**
*
* A class file buffer strategy that discards the original class file buffer.
*
*
* Warning: This strategy discards any changes that were applied by previous Java agents.
*
*/
DISCARDING {
/** {@inheritDoc} */
public ClassFileLocator resolve(String name,
byte[] binaryRepresentation,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module,
@MaybeNull ProtectionDomain protectionDomain) {
return ClassFileLocator.NoOp.INSTANCE;
}
/** {@inheritDoc} */
public TypePool typePool(PoolStrategy poolStrategy,
ClassFileLocator classFileLocator,
@MaybeNull ClassLoader classLoader,
String name) {
return poolStrategy.typePool(classFileLocator, classLoader);
}
}
}
}
/**
* A decorator that allows to change the class file transformer that is registered.
*/
interface TransformerDecorator {
/**
* Decorates the applied class file transformer.
*
* @param classFileTransformer The original transformer created by the agent builder.
* @return The class file transformer that is actually being registered.
*/
ResettableClassFileTransformer decorate(ResettableClassFileTransformer classFileTransformer);
/**
* A transformer decorator that retains the original transformer.
*/
enum NoOp implements TransformerDecorator {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public ResettableClassFileTransformer decorate(ResettableClassFileTransformer classFileTransformer) {
return classFileTransformer;
}
}
/**
* Wraps a class file transformer to become substitutable.
*/
enum ForSubstitution implements TransformerDecorator {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public ResettableClassFileTransformer decorate(ResettableClassFileTransformer classFileTransformer) {
return ResettableClassFileTransformer.WithDelegation.Substitutable.of(classFileTransformer);
}
}
/**
* A compound transformer decorator.
*/
@HashCodeAndEqualsPlugin.Enhance
class Compound implements TransformerDecorator {
/**
* The listeners to invoke.
*/
private final List transformerDecorators;
/**
* Creates a new compound transformer decorator.
*
* @param transformerDecorator The transformer decorators to add.
*/
public Compound(TransformerDecorator... transformerDecorator) {
this(Arrays.asList(transformerDecorator));
}
/**
* Creates a new compound listener.
*
* @param transformerDecorators The transformerDecorators to invoke.
*/
public Compound(List extends TransformerDecorator> transformerDecorators) {
this.transformerDecorators = new ArrayList();
for (TransformerDecorator transformerDecorator : transformerDecorators) {
if (transformerDecorator instanceof Compound) {
this.transformerDecorators.addAll(((Compound) transformerDecorator).transformerDecorators);
} else if (!(transformerDecorator instanceof NoOp)) {
this.transformerDecorators.add(transformerDecorator);
}
}
}
/**
* {@inheritDoc}
*/
public ResettableClassFileTransformer decorate(ResettableClassFileTransformer classFileTransformer) {
for (TransformerDecorator transformerDecorator : transformerDecorators) {
classFileTransformer = transformerDecorator.decorate(classFileTransformer);
}
return classFileTransformer;
}
}
}
/**
*
* A redefinition strategy regulates how already loaded classes are modified by a built agent.
*
*
* Important: Most JVMs do not support changes of a class's structure after a class was already
* loaded. Therefore, it is typically required that this class file transformer was built while enabling
* {@link AgentBuilder#disableClassFormatChanges()}.
*
*/
enum RedefinitionStrategy {
/**
* Disables redefinition such that already loaded classes are not affected by the agent.
*/
DISABLED(false, false) {
@Override
public void apply(Instrumentation instrumentation,
PoolStrategy poolStrategy, LocationStrategy locationStrategy, DescriptionStrategy descriptionStrategy, FallbackStrategy fallbackStrategy, DiscoveryStrategy discoveryStrategy, LambdaInstrumentationStrategy lambdaInstrumentationStrategy, AgentBuilder.Listener listener,
Listener redefinitionListener, RawMatcher matcher, BatchAllocator redefinitionBatchAllocator, CircularityLock circularityLock) {
/* do nothing */
}
@Override
protected void check(Instrumentation instrumentation) {
throw new IllegalStateException("Cannot apply redefinition on disabled strategy");
}
@Override
protected Collector make(PoolStrategy poolStrategy,
LocationStrategy locationStrategy,
DescriptionStrategy descriptionStrategy,
FallbackStrategy fallbackStrategy,
AgentBuilder.Listener listener,
RawMatcher matcher,
CircularityLock circularityLock) {
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.
*
*
* Using this strategy, a redefinition is applied as a single transformation request. This means that a single illegal
* redefinition of a class causes the entire redefinition attempt to fail.
*
*
* 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 constraints given by this type strategy.
*
*/
REDEFINITION(true, false) {
@Override
protected void check(Instrumentation instrumentation) {
if (!instrumentation.isRedefineClassesSupported()) {
throw new IllegalStateException("Cannot apply redefinition on " + instrumentation);
}
}
@Override
protected Collector make(PoolStrategy poolStrategy,
LocationStrategy locationStrategy,
DescriptionStrategy descriptionStrategy,
FallbackStrategy fallbackStrategy,
AgentBuilder.Listener listener,
RawMatcher matcher,
CircularityLock circularityLock) {
return new Collector.ForRedefinition(matcher,
poolStrategy,
locationStrategy,
descriptionStrategy,
listener,
fallbackStrategy,
circularityLock);
}
},
/**
*
* 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.
*
*
* Using this strategy, a retransformation is applied as a single transformation request. This means that a single illegal
* retransformation of a class causes the entire retransformation attempt to fail.
*
*
* Note: When applying a retransformation, 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 constraints given by this type strategy.
*
*/
RETRANSFORMATION(true, true) {
@Override
protected void check(Instrumentation instrumentation) {
if (!DISPATCHER.isRetransformClassesSupported(instrumentation)) {
throw new IllegalStateException("Cannot apply retransformation on " + instrumentation);
}
}
@Override
protected Collector make(PoolStrategy poolStrategy,
LocationStrategy locationStrategy,
DescriptionStrategy descriptionStrategy,
FallbackStrategy fallbackStrategy,
AgentBuilder.Listener listener,
RawMatcher matcher,
CircularityLock circularityLock) {
return new Collector.ForRetransformation(matcher,
poolStrategy,
locationStrategy,
descriptionStrategy,
listener,
fallbackStrategy,
circularityLock);
}
};
/**
* A dispatcher to use for interacting with the instrumentation API.
*/
protected static final Dispatcher DISPATCHER = Default.doPrivileged(JavaDispatcher.of(Dispatcher.class));
/**
* Indicates that this redefinition strategy is enabled.
*/
private final boolean enabled;
/**
* {@code true} if this strategy applies retransformation.
*/
private final boolean retransforming;
/**
* Creates a new redefinition strategy.
*
* @param enabled {@code true} if this strategy is enabled.
* @param retransforming {@code true} if this strategy applies retransformation.
*/
RedefinitionStrategy(boolean enabled, boolean retransforming) {
this.enabled = enabled;
this.retransforming = retransforming;
}
/**
* Indicates if this strategy requires a class file transformer to be registered with a hint to apply the
* transformer for retransformation.
*
* @return {@code true} if a class file transformer must be registered with a hint for retransformation.
*/
protected boolean isRetransforming() {
return retransforming;
}
/**
* Checks if this strategy can be applied to the supplied instrumentation instance.
*
* @param instrumentation The instrumentation instance to validate.
*/
protected abstract void check(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 enabled;
}
/**
* Creates a collector instance that is responsible for collecting loaded classes for potential retransformation.
*
* @param poolStrategy The pool strategy to use.
* @param locationStrategy The location strategy to use.
* @param descriptionStrategy The description strategy for resolving type descriptions for types.
* @param fallbackStrategy The fallback strategy to apply.
* @param listener The listener to notify on transformations.
* @param matcher The matcher to identify what types to redefine.
* @param circularityLock The circularity lock to use.
* @return A new collector for collecting already loaded classes for transformation.
*/
protected abstract Collector make(PoolStrategy poolStrategy,
LocationStrategy locationStrategy,
DescriptionStrategy descriptionStrategy,
FallbackStrategy fallbackStrategy,
AgentBuilder.Listener listener,
RawMatcher matcher,
CircularityLock circularityLock);
/**
* Applies this redefinition strategy by submitting all loaded types to redefinition. If this redefinition strategy is disabled,
* this method is non-operational.
*
* @param instrumentation The instrumentation instance to use.
* @param poolStrategy The type locator to use.
* @param locationStrategy The location strategy to use.
* @param descriptionStrategy The description strategy for resolving type descriptions for types.
* @param fallbackStrategy The fallback strategy to apply.
* @param redefinitionDiscoveryStrategy The discovery strategy for loaded types to be redefined.
* @param lambdaInstrumentationStrategy A strategy to determine of the {@code LambdaMetafactory} should be instrumented to allow for the
* instrumentation of classes that represent lambda expressions.
* @param listener The listener to notify on transformations.
* @param redefinitionListener The redefinition listener for the redefinition strategy to apply.
* @param matcher The matcher to identify what types to redefine.
* @param redefinitionBatchAllocator The batch allocator for the redefinition strategy to apply.
* @param circularityLock The circularity lock to use.
*/
protected void apply(Instrumentation instrumentation,
PoolStrategy poolStrategy,
LocationStrategy locationStrategy,
DescriptionStrategy descriptionStrategy,
FallbackStrategy fallbackStrategy,
DiscoveryStrategy redefinitionDiscoveryStrategy,
LambdaInstrumentationStrategy lambdaInstrumentationStrategy,
AgentBuilder.Listener listener,
Listener redefinitionListener,
RawMatcher matcher,
BatchAllocator redefinitionBatchAllocator,
CircularityLock circularityLock) {
check(instrumentation);
int batch = RedefinitionStrategy.BatchAllocator.FIRST_BATCH;
for (Iterable> types : redefinitionDiscoveryStrategy.resolve(instrumentation)) {
RedefinitionStrategy.Collector collector = make(poolStrategy,
locationStrategy,
descriptionStrategy,
fallbackStrategy,
listener,
matcher,
circularityLock);
for (Class> type : types) {
if (type == null || type.isArray() || type.isPrimitive() || !lambdaInstrumentationStrategy.isInstrumented(type)) {
continue;
}
collector.consider(type, DISPATCHER.isModifiableClass(instrumentation, type) || ClassFileVersion.ofThisVm(ClassFileVersion.JAVA_V5).isAtMost(ClassFileVersion.JAVA_V5));
}
batch = collector.apply(instrumentation, redefinitionBatchAllocator, redefinitionListener, batch);
}
}
/**
* A batch allocator which is responsible for applying a redefinition in a batches. A class redefinition or
* retransformation can be a time-consuming operation rendering a JVM non-responsive. In combination with a
* a {@link RedefinitionStrategy.Listener}, it is also possible to apply pauses between batches to distribute
* the load of a retransformation over time.
*/
public interface BatchAllocator {
/**
* The index of the first batch.
*/
int FIRST_BATCH = 0;
/**
* Splits a list of types to be retransformed into separate batches.
*
* @param types A list of types which should be retransformed.
* @return An iterable of retransformations within a batch.
*/
Iterable extends List>> batch(List> types);
/**
* A batch allocator that includes all types in a single batch.
*/
enum ForTotal implements BatchAllocator {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Iterable extends List>> batch(List> types) {
return types.isEmpty()
? Collections.>>emptySet()
: Collections.singleton(types);
}
}
/**
* A batch allocator that creates chunks with a fixed size as batch jobs.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForFixedSize implements BatchAllocator {
/**
* The size of each chunk.
*/
private final int size;
/**
* Creates a new batch allocator that creates fixed-sized chunks.
*
* @param size The size of each chunk.
*/
protected ForFixedSize(int size) {
this.size = size;
}
/**
* Creates a new batch allocator that creates chunks of a fixed size.
*
* @param size The size of each chunk or {@code 0} if the batch should be included in a single chunk.
* @return An appropriate batch allocator.
*/
public static BatchAllocator ofSize(int size) {
if (size > 0) {
return new ForFixedSize(size);
} else if (size == 0) {
return ForTotal.INSTANCE;
} else {
throw new IllegalArgumentException("Cannot define a batch with a negative size: " + size);
}
}
/**
* {@inheritDoc}
*/
public Iterable extends List>> batch(List> types) {
List>> batches = new ArrayList>>();
for (int index = 0; index < types.size(); index += size) {
batches.add(new ArrayList>(types.subList(index, Math.min(types.size(), index + size))));
}
return batches;
}
}
/**
* A batch allocator that groups all batches by discriminating types using a type matcher.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForMatchedGrouping implements BatchAllocator {
/**
* The type matchers to apply.
*/
private final Collection extends ElementMatcher super TypeDescription>> matchers;
/**
* Creates a new batch allocator that groups all batches by discriminating types using a type matcher. All batches
* are applied in their application order with any unmatched type being included in the last batch.
*
* @param matcher The type matchers to apply in their application order.
*/
@SuppressWarnings("unchecked") // In absence of @SafeVarargs
public ForMatchedGrouping(ElementMatcher super TypeDescription>... matcher) {
this(new LinkedHashSet>(Arrays.asList(matcher)));
}
/**
* Creates a new batch allocator that groups all batches by discriminating types using a type matcher. All batches
* are applied in their application order with any unmatched type being included in the last batch.
*
* @param matchers The type matchers to apply in their application order.
*/
public ForMatchedGrouping(Collection extends ElementMatcher super TypeDescription>> matchers) {
this.matchers = matchers;
}
/**
* Assures that any group is at least of a given size. If a group is smaller than a given size, it is merged with its types
* are merged with its subsequent group(s) as long as such groups exist.
*
* @param threshold The minimum threshold for any batch.
* @return An appropriate batch allocator.
*/
public BatchAllocator withMinimum(int threshold) {
return Slicing.withMinimum(threshold, this);
}
/**
* Assures that any group is at least of a given size. If a group is bigger than a given size, it is split into two several
* batches.
*
* @param threshold The maximum threshold for any batch.
* @return An appropriate batch allocator.
*/
public BatchAllocator withMaximum(int threshold) {
return Slicing.withMaximum(threshold, this);
}
/**
* Assures that any group is within a size range described by the supplied minimum and maximum. Groups are split and merged
* according to the supplied thresholds. The last group contains might be smaller than the supplied minimum.
*
* @param minimum The minimum threshold for any batch.
* @param maximum The maximum threshold for any batch.
* @return An appropriate batch allocator.
*/
public BatchAllocator withinRange(int minimum, int maximum) {
return Slicing.withinRange(minimum, maximum, this);
}
/**
* {@inheritDoc}
*/
public Iterable extends List>> batch(List> types) {
Map, List>> matched = new LinkedHashMap, List>>();
List> unmatched = new ArrayList>();
for (ElementMatcher super TypeDescription> matcher : matchers) {
matched.put(matcher, new ArrayList>());
}
typeLoop:
for (Class> type : types) {
for (ElementMatcher super TypeDescription> matcher : matchers) {
if (matcher.matches(TypeDescription.ForLoadedType.of(type))) {
matched.get(matcher).add(type);
continue typeLoop;
}
}
unmatched.add(type);
}
List>> batches = new ArrayList>>(matchers.size() + 1);
for (List> batch : matched.values()) {
if (!batch.isEmpty()) {
batches.add(batch);
}
}
if (!unmatched.isEmpty()) {
batches.add(unmatched);
}
return batches;
}
}
/**
* A slicing batch allocator that assures that any batch is within a certain size range.
*/
@HashCodeAndEqualsPlugin.Enhance
class Slicing implements BatchAllocator {
/**
* The minimum size of each slice.
*/
private final int minimum;
/**
* The maximum size of each slice.
*/
private final int maximum;
/**
* The delegate batch allocator.
*/
private final BatchAllocator batchAllocator;
/**
* Creates a new slicing batch allocator.
*
* @param minimum The minimum size of each slice.
* @param maximum The maximum size of each slice.
* @param batchAllocator The delegate batch allocator.
*/
protected Slicing(int minimum, int maximum, BatchAllocator batchAllocator) {
this.minimum = minimum;
this.maximum = maximum;
this.batchAllocator = batchAllocator;
}
/**
* Creates a new slicing batch allocator.
*
* @param minimum The minimum size of each slice.
* @param batchAllocator The delegate batch allocator.
* @return An appropriate slicing batch allocator.
*/
public static BatchAllocator withMinimum(int minimum, BatchAllocator batchAllocator) {
return withinRange(minimum, Integer.MAX_VALUE, batchAllocator);
}
/**
* Creates a new slicing batch allocator.
*
* @param maximum The maximum size of each slice.
* @param batchAllocator The delegate batch allocator.
* @return An appropriate slicing batch allocator.
*/
public static BatchAllocator withMaximum(int maximum, BatchAllocator batchAllocator) {
return withinRange(1, maximum, batchAllocator);
}
/**
* Creates a new slicing batch allocator.
*
* @param minimum The minimum size of each slice.
* @param maximum The maximum size of each slice.
* @param batchAllocator The delegate batch allocator.
* @return An appropriate slicing batch allocator.
*/
public static BatchAllocator withinRange(int minimum, int maximum, BatchAllocator batchAllocator) {
if (minimum <= 0) {
throw new IllegalArgumentException("Minimum must be a positive number: " + minimum);
} else if (minimum > maximum) {
throw new IllegalArgumentException("Minimum must not be bigger than maximum: " + minimum + " >" + maximum);
}
return new Slicing(minimum, maximum, batchAllocator);
}
/**
* {@inheritDoc}
*/
public Iterable extends List>> batch(List> types) {
return new SlicingIterable(minimum, maximum, batchAllocator.batch(types));
}
/**
* An iterable that slices batches into parts of a minimum and maximum size.
*/
protected static class SlicingIterable implements Iterable>> {
/**
* The minimum size of any slice.
*/
private final int minimum;
/**
* The maximum size of any slice.
*/
private final int maximum;
/**
* The delegate iterable.
*/
private final Iterable extends List>> iterable;
/**
* Creates a new slicing iterable.
*
* @param minimum The minimum size of any slice.
* @param maximum The maximum size of any slice.
* @param iterable The delegate iterable.
*/
protected SlicingIterable(int minimum, int maximum, Iterable extends List>> iterable) {
this.minimum = minimum;
this.maximum = maximum;
this.iterable = iterable;
}
/**
* {@inheritDoc}
*/
public Iterator>> iterator() {
return new SlicingIterator(minimum, maximum, iterable.iterator());
}
/**
* An iterator that slices batches into parts of a minimum and maximum size.
*/
protected static class SlicingIterator implements Iterator>> {
/**
* The minimum size of any slice.
*/
private final int minimum;
/**
* The maximum size of any slice.
*/
private final int maximum;
/**
* The delegate iterator.
*/
private final Iterator extends List>> iterator;
/**
* A buffer containing all types that surpassed the maximum.
*/
private List> buffer;
/**
* Creates a new slicing iterator.
*
* @param minimum The minimum size of any slice.
* @param maximum The maximum size of any slice.
* @param iterator The delegate iterator.
*/
protected SlicingIterator(int minimum, int maximum, Iterator extends List>> iterator) {
this.minimum = minimum;
this.maximum = maximum;
this.iterator = iterator;
buffer = new ArrayList>();
}
/**
* {@inheritDoc}
*/
public boolean hasNext() {
return !buffer.isEmpty() || iterator.hasNext();
}
/**
* {@inheritDoc}
*/
public List> next() {
if (buffer.isEmpty()) {
buffer = iterator.next();
}
while (buffer.size() < minimum && iterator.hasNext()) {
buffer.addAll(iterator.next());
}
if (buffer.size() > maximum) {
try {
return buffer.subList(0, maximum);
} finally {
buffer = new ArrayList>(buffer.subList(maximum, buffer.size()));
}
} else {
try {
return buffer;
} finally {
buffer = new ArrayList>();
}
}
}
/**
* {@inheritDoc}
*/
public void remove() {
throw new UnsupportedOperationException("remove");
}
}
}
}
/**
* A partitioning batch allocator that splits types for redefinition into a fixed amount of parts.
*/
@HashCodeAndEqualsPlugin.Enhance
class Partitioning implements BatchAllocator {
/**
* The amount of batches to generate.
*/
private final int parts;
/**
* Creates a new batch allocator that splits types for redefinition into a fixed amount of parts.
*
* @param parts The amount of parts to create.
*/
protected Partitioning(int parts) {
this.parts = parts;
}
/**
* Creates a part-splitting batch allocator.
*
* @param parts The amount of parts to create.
* @return A batch allocator that splits the redefined types into a fixed amount of batches.
*/
public static BatchAllocator of(int parts) {
if (parts < 1) {
throw new IllegalArgumentException("A batch size must be positive: " + parts);
}
return new Partitioning(parts);
}
/**
* {@inheritDoc}
*/
public Iterable extends List>> batch(List> types) {
if (types.isEmpty()) {
return Collections.emptyList();
} else {
List>> batches = new ArrayList>>();
int size = types.size() / parts, reminder = types.size() % parts;
for (int index = reminder; index < types.size(); index += size) {
batches.add(new ArrayList>(types.subList(index, index + size)));
}
if (batches.isEmpty()) {
return Collections.singletonList(types);
} else {
batches.get(0).addAll(0, types.subList(0, reminder));
return batches;
}
}
}
}
}
/**
* A listener to be applied during a redefinition.
*/
public interface Listener {
/**
* Invoked before applying a batch.
*
* @param index A running index of the batch starting at {@code 0}.
* @param batch The types included in this batch.
* @param types All types included in the redefinition.
*/
void onBatch(int index, List> batch, List> types);
/**
* Invoked upon an error during a batch. This method is not invoked if the failure handler handled this error.
*
* @param index A running index of the batch starting at {@code 0}.
* @param batch The types included in this batch.
* @param throwable The throwable that caused this invocation.
* @param types All types included in the redefinition.
* @return A set of classes which should be attempted to be redefined. Typically, this should be a subset of the classes
* contained in {@code batch} but not all classes.
*/
Iterable extends List>> onError(int index, List> batch, Throwable throwable, List> types);
/**
* Invoked upon completion of all batches.
*
* @param amount The total amount of batches that were executed.
* @param types All types included in the redefinition.
* @param failures A mapping of batch types to their unhandled failures.
*/
void onComplete(int amount, List> types, Map>, Throwable> failures);
/**
* A non-operational listener.
*/
enum NoOp implements Listener {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public void onBatch(int index, List> batch, List> types) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public Iterable extends List>> onError(int index, List> batch, Throwable throwable, List> types) {
return Collections.emptyList();
}
/**
* {@inheritDoc}
*/
public void onComplete(int amount, List> types, Map>, Throwable> failures) {
/* do nothing */
}
}
/**
* A listener that invokes {@link Thread#yield()} prior to every batch but the first batch.
*/
enum Yielding implements Listener {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public void onBatch(int index, List> batch, List> types) {
if (index > 0) {
Thread.yield();
}
}
/**
* {@inheritDoc}
*/
public Iterable extends List>> onError(int index, List> batch, Throwable throwable, List> types) {
return Collections.emptyList();
}
/**
* {@inheritDoc}
*/
public void onComplete(int amount, List> types, Map>, Throwable> failures) {
/* do nothing */
}
}
/**
* A listener that halts a retransformation process upon an exception.
*/
enum ErrorEscalating implements Listener {
/**
* A listener that fails the retransformation upon the first failed retransformation of a batch.
*/
FAIL_FAST {
/** {@inheritDoc} */
public Iterable extends List>> onError(int index, List> batch, Throwable throwable, List> types) {
throw new IllegalStateException("Could not transform any of " + batch, throwable);
}
/** {@inheritDoc} */
public void onComplete(int amount, List> types, Map>, Throwable> failures) {
/* do nothing */
}
},
/**
* A listener that fails the retransformation after all batches were executed if any error occurred.
*/
FAIL_LAST {
/** {@inheritDoc} */
public Iterable extends List>> onError(int index, List> batch, Throwable throwable, List> types) {
return Collections.emptyList();
}
/** {@inheritDoc} */
public void onComplete(int amount, List> types, Map>, Throwable> failures) {
if (!failures.isEmpty()) {
throw new IllegalStateException("Could not transform any of " + failures);
}
}
};
/**
* {@inheritDoc}
*/
public void onBatch(int index, List> batch, List> types) {
/* do nothing */
}
}
/**
* A listener adapter that offers non-operational implementations of all listener methods.
*/
@HashCodeAndEqualsPlugin.Enhance
abstract class Adapter implements Listener {
/**
* {@inheritDoc}
*/
public void onBatch(int index, List> batch, List> types) {
/* do nothing */
}
/**
* {@inheritDoc}
*/
public Iterable extends List>> onError(int index, List> batch, Throwable throwable, List> types) {
return Collections.emptyList();
}
/**
* {@inheritDoc}
*/
public void onComplete(int amount, List> types, Map>, Throwable> failures) {
/* do nothing */
}
}
/**
*
* A batch reallocator allows to split up a failed retransformation into additional batches which are reenqueed to the
* current retransformation process. To do so, any batch with at least to classes is rerouted through a {@link BatchAllocator}
* which is responsible for regrouping the classes that failed to be retransformed into new batches.
*
*
* Important: To avoid endless looping over classes that cannot be successfully retransformed, the supplied batch
* allocator must not resubmit batches that previously failed as an identical outcome is likely.
*
*/
@HashCodeAndEqualsPlugin.Enhance
class BatchReallocator extends Adapter {
/**
* The batch allocator to use for reallocating failed batches.
*/
private final BatchAllocator batchAllocator;
/**
* Creates a new batch reallocator.
*
* @param batchAllocator The batch allocator to use for reallocating failed batches.
*/
public BatchReallocator(BatchAllocator batchAllocator) {
this.batchAllocator = batchAllocator;
}
/**
* Creates a batch allocator that splits any batch into two parts and resubmits these parts as two batches.
*
* @return A batch reallocating batch listener that splits failed batches into two parts for resubmission.
*/
public static Listener splitting() {
return new BatchReallocator(new BatchAllocator.Partitioning(2));
}
@Override
public Iterable extends List>> onError(int index, List> batch, Throwable throwable, List> types) {
return batch.size() < 2
? Collections.>>emptyList()
: batchAllocator.batch(batch);
}
}
/**
* A listener that invokes {@link Thread#sleep(long)} prior to every batch but the first batch.
*/
@HashCodeAndEqualsPlugin.Enhance
class Pausing extends Adapter {
/**
* The time to sleep in milliseconds between every two batches.
*/
private final long value;
/**
* Creates a new pausing listener.
*
* @param value The time to sleep in milliseconds between every two batches.
*/
protected Pausing(long value) {
this.value = value;
}
/**
* Creates a listener that pauses for the specified amount of time. If the specified value is {@code 0}, a
* non-operational listener is returned.
*
* @param value The amount of time to pause between redefinition batches.
* @param timeUnit The time unit of {@code value}.
* @return An appropriate listener.
*/
public static Listener of(long value, TimeUnit timeUnit) {
if (value > 0L) {
return new Pausing(timeUnit.toMillis(value));
} else if (value == 0L) {
return NoOp.INSTANCE;
} else {
throw new IllegalArgumentException("Cannot sleep for a non-positive amount of time: " + value);
}
}
@Override
public void onBatch(int index, List> batch, List> types) {
if (index > 0) {
try {
Thread.sleep(value);
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
throw new IllegalStateException(exception);
}
}
}
}
/**
* A listener that writes events to a {@link PrintStream}.
*/
@HashCodeAndEqualsPlugin.Enhance
class StreamWriting implements Listener {
/**
* The print stream to write any events to.
*/
private final PrintStream printStream;
/**
* Creates a new stream writing listener.
*
* @param printStream The print stream to write any events to.
*/
public StreamWriting(PrintStream printStream) {
this.printStream = printStream;
}
/**
* Writes the stream result to {@link System#out}.
*
* @return An appropriate listener.
*/
public static Listener toSystemOut() {
return new StreamWriting(System.out);
}
/**
* Writes the stream result to {@link System#err}.
*
* @return An appropriate listener.
*/
public static Listener toSystemError() {
return new StreamWriting(System.err);
}
/**
* {@inheritDoc}
*/
public void onBatch(int index, List> batch, List> types) {
printStream.printf(AgentBuilder.Listener.StreamWriting.PREFIX + " REDEFINE BATCH #%d [%d of %d type(s)]%n", index, batch.size(), types.size());
}
/**
* {@inheritDoc}
*/
public Iterable extends List>> onError(int index, List> batch, Throwable throwable, List> types) {
synchronized (printStream) {
printStream.printf(AgentBuilder.Listener.StreamWriting.PREFIX + " REDEFINE ERROR #%d [%d of %d type(s)]%n", index, batch.size(), types.size());
throwable.printStackTrace(printStream);
}
return Collections.emptyList();
}
/**
* {@inheritDoc}
*/
public void onComplete(int amount, List> types, Map>, Throwable> failures) {
printStream.printf(AgentBuilder.Listener.StreamWriting.PREFIX + " REDEFINE COMPLETE %d batch(es) containing %d types [%d failed batch(es)]%n", amount, types.size(), failures.size());
}
}
/**
* A compound listener that delegates events to several listeners.
*/
@HashCodeAndEqualsPlugin.Enhance
class Compound implements Listener {
/**
* The listeners to invoke.
*/
private final List listeners;
/**
* Creates a new compound listener.
*
* @param listener The listeners to invoke.
*/
public Compound(Listener... listener) {
this(Arrays.asList(listener));
}
/**
* Creates a new compound listener.
*
* @param listeners The listeners to invoke.
*/
public Compound(List extends Listener> listeners) {
this.listeners = new ArrayList();
for (Listener listener : listeners) {
if (listener instanceof Compound) {
this.listeners.addAll(((Compound) listener).listeners);
} else if (!(listener instanceof NoOp)) {
this.listeners.add(listener);
}
}
}
/**
* {@inheritDoc}
*/
public void onBatch(int index, List> batch, List> types) {
for (Listener listener : listeners) {
listener.onBatch(index, batch, types);
}
}
/**
* {@inheritDoc}
*/
public Iterable extends List>> onError(int index, List> batch, Throwable throwable, List> types) {
List>>> reattempts = new ArrayList>>>();
for (Listener listener : listeners) {
reattempts.add(listener.onError(index, batch, throwable, types));
}
return new CompoundIterable(reattempts);
}
/**
* {@inheritDoc}
*/
public void onComplete(int amount, List> types, Map>, Throwable> failures) {
for (Listener listener : listeners) {
listener.onComplete(amount, types, failures);
}
}
/**
* A compound iterable.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class CompoundIterable implements Iterable>> {
/**
* The iterables to consider.
*/
private final List>>> iterables;
/**
* Creates a compound iterable.
*
* @param iterables The iterables to consider.
*/
protected CompoundIterable(List>>> iterables) {
this.iterables = iterables;
}
/**
* {@inheritDoc}
*/
public Iterator>> iterator() {
return new CompoundIterator(new ArrayList>>>(iterables));
}
/**
* A compound iterator that combines several iterables.
*/
protected static class CompoundIterator implements Iterator>> {
/**
* The current iterator or {@code null} if no such iterator is defined.
*/
@MaybeNull
private Iterator extends List>> current;
/**
* A backlog of iterables to still consider.
*/
private final List>>> backlog;
/**
* Creates a compound iterator.
*
* @param iterables The iterables to consider.
*/
protected CompoundIterator(List>>> iterables) {
backlog = iterables;
forward();
}
/**
* {@inheritDoc}
*/
public boolean hasNext() {
return current != null && current.hasNext();
}
/**
* {@inheritDoc}
*/
public List> next() {
try {
if (current != null) {
return current.next();
} else {
throw new NoSuchElementException();
}
} finally {
forward();
}
}
/**
* Forwards the iterator to the next relevant iterable.
*/
private void forward() {
while ((current == null || !current.hasNext()) && !backlog.isEmpty()) {
current = backlog.remove(0).iterator();
}
}
/**
* {@inheritDoc}
*/
public void remove() {
throw new UnsupportedOperationException("remove");
}
}
}
}
}
/**
* A strategy for discovering types to redefine.
*/
public interface DiscoveryStrategy {
/**
* Resolves an iterable of types to retransform. Types might be loaded during a previous retransformation which might require
* multiple passes for a retransformation.
*
* @param instrumentation The instrumentation instance used for the redefinition.
* @return An iterable of types to consider for retransformation.
*/
Iterable>> resolve(Instrumentation instrumentation);
/**
* A discovery strategy that considers all loaded types supplied by {@link Instrumentation#getAllLoadedClasses()}.
*/
enum SinglePass implements DiscoveryStrategy {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Iterable>> resolve(Instrumentation instrumentation) {
return Collections.>>singleton(Arrays.>asList(instrumentation.getAllLoadedClasses()));
}
}
/**
* A discovery strategy that considers all loaded types supplied by {@link Instrumentation#getAllLoadedClasses()}. For each reiteration,
* this strategy checks if additional types were loaded after the previously supplied types. Doing so, types that were loaded during
* instrumentations can be retransformed as such types are not passed to any class file transformer.
*/
enum Reiterating implements DiscoveryStrategy {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Iterable>> resolve(Instrumentation instrumentation) {
return new ReiteratingIterable(instrumentation);
}
/**
* An iterable that returns any loaded types and checks if any additional types were loaded during the last instrumentation.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class ReiteratingIterable implements Iterable>> {
/**
* The instrumentation instance to use.
*/
private final Instrumentation instrumentation;
/**
* Creates a new reiterating iterable.
*
* @param instrumentation The instrumentation instance to use.
*/
protected ReiteratingIterable(Instrumentation instrumentation) {
this.instrumentation = instrumentation;
}
/**
* {@inheritDoc}
*/
public Iterator>> iterator() {
return new ReiteratingIterator(instrumentation);
}
}
/**
* A reiterating iterator that considers types that were loaded during an instrumentation.
*/
protected static class ReiteratingIterator implements Iterator>> {
/**
* The instrumentation instance to use.
*/
private final Instrumentation instrumentation;
/**
* A set containing all previously discovered types.
*/
private final Set> processed;
/**
* The current list of types or {@code null} if the current list of types is not prepared.
*/
@MaybeNull
private List> types;
/**
* Creates a new reiterating iterator.
*
* @param instrumentation The instrumentation instance to use.
*/
protected ReiteratingIterator(Instrumentation instrumentation) {
this.instrumentation = instrumentation;
processed = new HashSet>();
}
/**
* {@inheritDoc}
*/
public boolean hasNext() {
if (types == null) {
types = new ArrayList>();
for (Class> type : instrumentation.getAllLoadedClasses()) {
if (type != null && processed.add(type)) {
types.add(type);
}
}
}
return !types.isEmpty();
}
/**
* {@inheritDoc}
*/
public Iterable> next() {
if (hasNext()) {
try {
return types;
} finally {
types = null;
}
} else {
throw new NoSuchElementException();
}
}
/**
* {@inheritDoc}
*/
public void remove() {
throw new UnsupportedOperationException("remove");
}
}
/**
*
* A discovery strategy that simplifies the application of {@link Reiterating} by assuming that the
* loaded classes that are returned by {@link Instrumentation#getAllLoadedClasses()} are always
* returned in the same order.
*
*
* Important: While this increases the performance of reiteration, it relies on an implementation
* detail of the JVM. Also, this strategy does not consider the possibility of classes being unloaded
* during reiteration. For these reasons, this strategy has to be used with care!
*
*/
public enum WithSortOrderAssumption implements DiscoveryStrategy {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Iterable>> resolve(Instrumentation instrumentation) {
return new OrderedReiteratingIterable(instrumentation);
}
/**
* An iterable that reiterates over an array of loaded classes by the previously observed length.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class OrderedReiteratingIterable implements Iterable>> {
/**
* The instrumentation instance to use.
*/
private final Instrumentation instrumentation;
/**
* Creates a new reiterating iterable.
*
* @param instrumentation The instrumentation instance to use.
*/
protected OrderedReiteratingIterable(Instrumentation instrumentation) {
this.instrumentation = instrumentation;
}
/**
* {@inheritDoc}
*/
public Iterator>> iterator() {
return new OrderedReiteratingIterator(instrumentation);
}
}
/**
* An iterator that reiterates over an array of loaded classes by the previously observed length.
*/
protected static class OrderedReiteratingIterator implements Iterator>> {
/**
* The instrumentation instance to use.
*/
private final Instrumentation instrumentation;
/**
* The length of the last known array of known classes.
*/
private int index;
/**
* The current list of types or {@code null} if the current list of types is not prepared.
*/
@MaybeNull
private List> types;
/**
* Creates a new reiterating iterator.
*
* @param instrumentation The instrumentation instance to use.
*/
protected OrderedReiteratingIterator(Instrumentation instrumentation) {
this.instrumentation = instrumentation;
index = 0;
}
/**
* {@inheritDoc}
*/
public boolean hasNext() {
if (types == null) {
Class>[] type = instrumentation.getAllLoadedClasses();
types = new ArrayList>(Arrays.asList(type).subList(index, type.length));
index = type.length;
}
return !types.isEmpty();
}
/**
* {@inheritDoc}
*/
public Iterable> next() {
if (hasNext()) {
try {
return types;
} finally {
types = null;
}
} else {
throw new NoSuchElementException();
}
}
/**
* {@inheritDoc}
*/
public void remove() {
throw new UnsupportedOperationException("remove");
}
}
}
}
/**
* An explicit discovery strategy that only attempts the redefinition of specific types.
*/
@HashCodeAndEqualsPlugin.Enhance
class Explicit implements DiscoveryStrategy {
/**
* The types to redefine.
*/
private final Set> types;
/**
* Creates a new explicit discovery strategy.
*
* @param type The types to redefine.
*/
public Explicit(Class>... type) {
this(new LinkedHashSet>(Arrays.asList(type)));
}
/**
* Creates a new explicit discovery strategy.
*
* @param types The types to redefine.
*/
public Explicit(Set> types) {
this.types = types;
}
/**
* {@inheritDoc}
*/
public Iterable>> resolve(Instrumentation instrumentation) {
return Collections.>>singleton(types);
}
}
}
/**
* A resubmission scheduler is responsible for scheduling a job that is resubmitting unloaded types that failed during retransformation.
*/
public interface ResubmissionScheduler {
/**
* Checks if this scheduler is currently available.
*
* @return {@code true} if this scheduler is alive.
*/
boolean isAlive();
/**
* Schedules a resubmission job for regular application.
*
* @param job The job to schedule.
* @return A cancelable that is canceled upon resetting the corresponding class file transformer.
*/
Cancelable schedule(Runnable job);
/**
* A cancelable allows to discontinue a resubmission job.
*/
interface Cancelable {
/**
* Cancels this resubmission job.
*/
void cancel();
/**
* A non-operational cancelable.
*/
enum NoOp implements Cancelable {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public void cancel() {
/* do nothing */
}
}
/**
* A cancelable for a {@link Future}.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForFuture implements Cancelable {
/**
* The future to cancel upon cancellation of this instance.
*/
private final Future> future;
/**
* Creates a cancelable for a future.
*
* @param future The future to cancel upon cancellation of this instance.
*/
public ForFuture(Future> future) {
this.future = future;
}
/**
* {@inheritDoc}
*/
public void cancel() {
future.cancel(true);
}
}
}
/**
* A resubmission scheduler that does not apply any scheduling.
*/
enum NoOp implements ResubmissionScheduler {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public boolean isAlive() {
return false;
}
/**
* {@inheritDoc}
*/
public Cancelable schedule(Runnable job) {
return Cancelable.NoOp.INSTANCE;
}
}
/**
* A resubmission scheduler that schedules jobs at a fixed rate.
*/
@HashCodeAndEqualsPlugin.Enhance
class AtFixedRate implements ResubmissionScheduler {
/**
* The executor service to schedule to.
*/
private final ScheduledExecutorService scheduledExecutorService;
/**
* The time interval between schedulings.
*/
private final long time;
/**
* The time's time unit.
*/
private final TimeUnit timeUnit;
/**
* Creates a new resubmission scheduler which schedules executions at a fixed rate.
*
* @param scheduledExecutorService The executor service to schedule to.
* @param time The time interval between schedulings.
* @param timeUnit The time's time unit.
*/
public AtFixedRate(ScheduledExecutorService scheduledExecutorService, long time, TimeUnit timeUnit) {
this.scheduledExecutorService = scheduledExecutorService;
this.time = time;
this.timeUnit = timeUnit;
}
/**
* {@inheritDoc}
*/
public boolean isAlive() {
return !scheduledExecutorService.isShutdown();
}
/**
* {@inheritDoc}
*/
public Cancelable schedule(Runnable job) {
return new Cancelable.ForFuture(scheduledExecutorService.scheduleAtFixedRate(job, time, time, timeUnit));
}
}
/**
* A resubmission scheduler that schedules jobs with a fixed delay.
*/
@HashCodeAndEqualsPlugin.Enhance
class WithFixedDelay implements ResubmissionScheduler {
/**
* The executor service to schedule to.
*/
private final ScheduledExecutorService scheduledExecutorService;
/**
* The time interval to pause between completed jobs.
*/
private final long time;
/**
* The time's time unit.
*/
private final TimeUnit timeUnit;
/**
* Creates a new resubmission scheduler with a fixed delay between job executions.
*
* @param scheduledExecutorService The executor service to schedule to.
* @param time The time interval to pause between completed jobs.
* @param timeUnit The time's time unit.
*/
public WithFixedDelay(ScheduledExecutorService scheduledExecutorService, long time, TimeUnit timeUnit) {
this.scheduledExecutorService = scheduledExecutorService;
this.time = time;
this.timeUnit = timeUnit;
}
/**
* {@inheritDoc}
*/
public boolean isAlive() {
return !scheduledExecutorService.isShutdown();
}
/**
* {@inheritDoc}
*/
public Cancelable schedule(Runnable job) {
return new Cancelable.ForFuture(scheduledExecutorService.scheduleWithFixedDelay(job, time, time, timeUnit));
}
}
}
/**
* A resubmission strategy is responsible for enabling resubmission of types that failed to resubmit.
*/
protected interface ResubmissionStrategy {
/**
* Invoked upon installation of an agent builder.
*
* @param instrumentation The instrumentation instance to use.
* @param poolStrategy The pool strategy to use.
* @param locationStrategy The location strategy to use.
* @param descriptionStrategy The description strategy to use.
* @param fallbackStrategy The fallback strategy to use.
* @param listener The listener to use.
* @param installationListener The installation listener to use.
* @param circularityLock The circularity lock to use.
* @param matcher The matcher to apply for analyzing if a type is to be resubmitted.
* @param redefinitionStrategy The redefinition strategy to use.
* @param redefinitionBatchAllocator The batch allocator to use.
* @param redefinitionBatchListener The batch listener to notify.
* @return A potentially modified listener to apply.
*/
Installation apply(Instrumentation instrumentation,
PoolStrategy poolStrategy,
LocationStrategy locationStrategy,
DescriptionStrategy descriptionStrategy,
FallbackStrategy fallbackStrategy,
AgentBuilder.Listener listener,
InstallationListener installationListener,
CircularityLock circularityLock,
RawMatcher matcher,
RedefinitionStrategy redefinitionStrategy,
RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
RedefinitionStrategy.Listener redefinitionBatchListener);
/**
* A disabled resubmission strategy.
*/
enum Disabled implements ResubmissionStrategy {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Installation apply(Instrumentation instrumentation,
PoolStrategy poolStrategy,
LocationStrategy locationStrategy,
DescriptionStrategy descriptionStrategy,
FallbackStrategy fallbackStrategy,
AgentBuilder.Listener listener,
InstallationListener installationListener,
CircularityLock circularityLock,
RawMatcher matcher,
RedefinitionStrategy redefinitionStrategy,
RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
RedefinitionStrategy.Listener redefinitionBatchListener) {
return new Installation(listener, installationListener, ResubmissionEnforcer.Disabled.INSTANCE);
}
}
/**
* An enabled resubmission strategy.
*/
@HashCodeAndEqualsPlugin.Enhance
class Enabled implements ResubmissionStrategy {
/**
* A scheduler that is responsible for resubmission of types.
*/
private final ResubmissionScheduler resubmissionScheduler;
/**
* A matcher to determine resubmissions on errors.
*/
private final RedefinitionListenable.ResubmissionOnErrorMatcher resubmissionOnErrorMatcher;
/**
* A matcher to determine resubmissions without errors.
*/
private final RedefinitionListenable.ResubmissionImmediateMatcher resubmissionImmediateMatcher;
/**
* Creates a new enabled resubmission strategy.
*
* @param resubmissionScheduler A scheduler that is responsible for resubmission of types.
* @param resubmissionOnErrorMatcher A matcher to determine resubmissions on errors.
* @param resubmissionImmediateMatcher A matcher to determine resubmissions without errors.
*/
protected Enabled(ResubmissionScheduler resubmissionScheduler,
RedefinitionListenable.ResubmissionOnErrorMatcher resubmissionOnErrorMatcher,
RedefinitionListenable.ResubmissionImmediateMatcher resubmissionImmediateMatcher) {
this.resubmissionScheduler = resubmissionScheduler;
this.resubmissionOnErrorMatcher = resubmissionOnErrorMatcher;
this.resubmissionImmediateMatcher = resubmissionImmediateMatcher;
}
/**
* {@inheritDoc}
*/
public Installation apply(Instrumentation instrumentation,
PoolStrategy poolStrategy,
LocationStrategy locationStrategy,
DescriptionStrategy descriptionStrategy,
FallbackStrategy fallbackStrategy,
AgentBuilder.Listener listener,
InstallationListener installationListener,
CircularityLock circularityLock,
RawMatcher matcher,
RedefinitionStrategy redefinitionStrategy,
RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
RedefinitionStrategy.Listener redefinitionBatchListener) {
if (resubmissionScheduler.isAlive()) {
ConcurrentMap> types = new ConcurrentHashMap