// Generated by delombok at Sun Feb 26 12:31:38 KST 2017
package scouter.bytebuddy.dynamic.loading;
import scouter.bytebuddy.description.type.TypeDescription;
import scouter.bytebuddy.dynamic.ClassFileLocator;
import scouter.bytebuddy.ByteBuddy;
import java.io.File;
import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* <p>
* The class reloading strategy allows to redefine loaded {@link java.lang.Class}es. Note that this strategy
* will always attempt to load an existing class prior to its redefinition, even if this class is not yet loaded.
* </p>
* <p>
* <b>Note</b>: In order to redefine any type, neither its name or its modifiers must be changed. Furthermore, no
* fields or methods must be removed or added. This makes this strategy generally incompatible to applying it to a
* rebased class definition as by {@link ByteBuddy#rebase(Class)} which copies the original method
* implementations to additional methods. Furthermore, even the {@link ByteBuddy#redefine(Class)}
* adds a method if the original class contains an explicit <i>class initializer</i>. For these reasons, it is not
* recommended to use this {@link ClassLoadingStrategy} with arbitrary classes.
* </p>
*/
public class ClassReloadingStrategy implements ClassLoadingStrategy<ClassLoader> {
/**
* The name of the Byte Buddy {@code net.bytebuddy.agent.Installer} class.
*/
private static final String INSTALLER_TYPE = "net.bytebuddy.agent.Installer";
/**
* The name of the {@code net.bytebuddy.agent.Installer} getter for reading an installed {@link Instrumentation}.
*/
private static final String INSTRUMENTATION_GETTER = "getInstrumentation";
/**
* Indicator for access to a static member via reflection to make the code more readable.
*/
private static final Object STATIC_MEMBER = null;
/**
* This instance's instrumentation.
*/
private final Instrumentation instrumentation;
/**
* An strategy which performs the actual redefinition of a {@link java.lang.Class}.
*/
private final Strategy strategy;
/**
* The strategy to apply for injecting classes into the bootstrap class loader.
*/
private final BootstrapInjection bootstrapInjection;
/**
* The preregistered types of this instance.
*/
private final Map<String, Class<?>> preregisteredTypes;
/**
* Creates a class reloading strategy for the given instrumentation using an explicit transformation strategy which
* is represented by an {@link Strategy}. The given instrumentation
* must support the strategy's transformation type.
*
* @param instrumentation The instrumentation to be used by this reloading strategy.
* @param strategy A strategy which performs the actual redefinition of a {@link java.lang.Class}.
*/
public ClassReloadingStrategy(Instrumentation instrumentation, Strategy strategy) {
this(instrumentation, strategy, BootstrapInjection.Disabled.INSTANCE, Collections.<String, Class<?>>emptyMap());
}
/**
* Creates a new class reloading strategy.
*
* @param instrumentation The instrumentation to be used by this reloading strategy.
* @param strategy An strategy which performs the actual redefinition of a {@link java.lang.Class}.
* @param bootstrapInjection The bootstrap class loader injection strategy to use.
* @param preregisteredTypes The preregistered types of this instance.
*/
protected ClassReloadingStrategy(Instrumentation instrumentation, Strategy strategy, BootstrapInjection bootstrapInjection, Map<String, Class<?>> preregisteredTypes) {
this.instrumentation = instrumentation;
this.strategy = strategy.validate(instrumentation);
this.bootstrapInjection = bootstrapInjection;
this.preregisteredTypes = preregisteredTypes;
}
/**
* Creates a class reloading strategy for the given instrumentation. The given instrumentation must either
* support {@link java.lang.instrument.Instrumentation#isRedefineClassesSupported()} or
* {@link java.lang.instrument.Instrumentation#isRetransformClassesSupported()}. If both modes are supported,
* classes will be transformed using a class retransformation.
*
* @param instrumentation The instrumentation to be used by this reloading strategy.
* @return A suitable class reloading strategy.
*/
public static ClassReloadingStrategy of(Instrumentation instrumentation) {
Strategy strategy;
if (instrumentation.isRedefineClassesSupported()) {
strategy = Strategy.REDEFINITION;
} else if (instrumentation.isRetransformClassesSupported()) {
strategy = Strategy.RETRANSFORMATION;
} else {
throw new IllegalArgumentException("Instrumentation does not support manipulation of loaded classes: " + instrumentation);
}
return new ClassReloadingStrategy(instrumentation, strategy);
}
/**
* <p>
* Obtains a {@link ClassReloadingStrategy} from an installed Byte Buddy agent. This
* agent must be installed either by adding the {@code byte-buddy-agent.jar} when starting up the JVM by
* </p>
* <p>
* <code>
* java -javaagent:byte-buddy-agent.jar -jar app.jar
* </code>
* </p>
* or after the start up using the Attach API. A convenience installer for the OpenJDK is provided by the
* {@code ByteBuddyAgent} within the {@code byte-buddy-agent} module.
*
* @return A class reloading strategy which uses the Byte Buddy agent's {@link java.lang.instrument.Instrumentation}.
*/
public static ClassReloadingStrategy fromInstalledAgent() {
try {
return ClassReloadingStrategy.of((Instrumentation) ClassLoader.getSystemClassLoader().loadClass(INSTALLER_TYPE).getMethod(INSTRUMENTATION_GETTER).invoke(STATIC_MEMBER));
} catch (RuntimeException exception) {
throw exception;
} catch (Exception exception) {
throw new IllegalStateException("The Byte Buddy agent is not installed or not accessible", exception);
}
}
@Override
public Map<TypeDescription, Class<?>> load(ClassLoader classLoader, Map<TypeDescription, byte[]> types) {
Map<String, Class<?>> availableTypes = new HashMap<String, Class<?>>(preregisteredTypes);
for (Class<?> type : instrumentation.getInitiatedClasses(classLoader)) {
availableTypes.put(TypeDescription.ForLoadedType.getName(type), type);
}
Map<Class<?>, ClassDefinition> classDefinitions = new ConcurrentHashMap<Class<?>, ClassDefinition>();
Map<TypeDescription, Class<?>> loadedClasses = new HashMap<TypeDescription, Class<?>>();
Map<TypeDescription, byte[]> unloadedClasses = new LinkedHashMap<TypeDescription, byte[]>();
for (Map.Entry<TypeDescription, byte[]> entry : types.entrySet()) {
Class<?> type = availableTypes.get(entry.getKey().getName());
if (type != null) {
classDefinitions.put(type, new ClassDefinition(type, entry.getValue()));
loadedClasses.put(entry.getKey(), type);
} else {
unloadedClasses.put(entry.getKey(), entry.getValue());
}
}
try {
strategy.apply(instrumentation, classDefinitions);
if (!unloadedClasses.isEmpty()) {
loadedClasses.putAll((classLoader == null ? bootstrapInjection.make(instrumentation) : new ClassInjector.UsingReflection(classLoader)).inject(unloadedClasses));
}
} catch (ClassNotFoundException exception) {
throw new IllegalArgumentException("Could not locate classes for redefinition", exception);
} catch (UnmodifiableClassException exception) {
throw new IllegalStateException("Cannot redefine specified class", exception);
}
return loadedClasses;
}
/**
* Resets all classes to their original definition while using the first type's class loader as a class file locator.
*
* @param type The types to reset.
* @return This class reloading strategy.
* @throws IOException If a class file locator causes an IO exception.
*/
public ClassReloadingStrategy reset(Class<?>... type) throws IOException {
return type.length == 0 ? this : reset(ClassFileLocator.ForClassLoader.of(type[0].getClassLoader()), type);
}
/**
* Resets all classes to their original definition.
*
* @param classFileLocator The class file locator to use.
* @param type The types to reset.
* @return This class reloading strategy.
* @throws IOException If a class file locator causes an IO exception.
*/
public ClassReloadingStrategy reset(ClassFileLocator classFileLocator, Class<?>... type) throws IOException {
if (type.length > 0) {
try {
strategy.reset(instrumentation, classFileLocator, Arrays.asList(type));
} catch (ClassNotFoundException exception) {
throw new IllegalArgumentException("Cannot locate types " + Arrays.toString(type), exception);
} catch (UnmodifiableClassException exception) {
throw new IllegalStateException("Cannot reset types " + Arrays.toString(type), exception);
}
}
return this;
}
/**
* Enables bootstrap injection for this class reloading strategy.
*
* @param folder The folder to save jar files in that are appended to the bootstrap class path.
* @return A class reloading strategy with bootstrap injection enabled.
*/
public ClassReloadingStrategy enableBootstrapInjection(File folder) {
return new ClassReloadingStrategy(instrumentation, strategy, new BootstrapInjection.Enabled(folder), preregisteredTypes);
}
/**
* Registers a type to be explicitly available without explicit lookup.
*
* @param type The loaded types that are explicitly available.
* @return This class reloading strategy with the given types being explicitly available.
*/
public ClassReloadingStrategy preregistered(Class<?>... type) {
Map<String, Class<?>> preregisteredTypes = new HashMap<String, Class<?>>(this.preregisteredTypes);
for (Class<?> aType : type) {
preregisteredTypes.put(TypeDescription.ForLoadedType.getName(aType), aType);
}
return new ClassReloadingStrategy(instrumentation, strategy, bootstrapInjection, preregisteredTypes);
}
/**
* A strategy which performs the actual redefinition of a {@link java.lang.Class}.
*/
public enum Strategy {
/**
* <p>
* Redefines a class using {@link java.lang.instrument.Instrumentation#redefineClasses(java.lang.instrument.ClassDefinition...)}.
* </p>
* <p>
* This strategy can be more efficient. However, the redefinition strategy does not allow to reset VM anonymous classes (e.g.
* classes that represent lambda expressions).
* </p>
*/
REDEFINITION(true) {
@Override
protected void apply(Instrumentation instrumentation, Map<Class<?>, ClassDefinition> classDefinitions) throws UnmodifiableClassException, ClassNotFoundException {
instrumentation.redefineClasses(classDefinitions.values().toArray(new ClassDefinition[classDefinitions.size()]));
}
@Override
protected Strategy validate(Instrumentation instrumentation) {
if (!instrumentation.isRedefineClassesSupported()) {
throw new IllegalArgumentException("Does not support redefinition: " + instrumentation);
}
return this;
}
@Override
public void reset(Instrumentation instrumentation, ClassFileLocator classFileLocator, List<Class<?>> types) throws IOException, UnmodifiableClassException, ClassNotFoundException {
Map<Class<?>, ClassDefinition> classDefinitions = new HashMap<Class<?>, ClassDefinition>(types.size());
for (Class<?> type : types) {
classDefinitions.put(type, new ClassDefinition(type, classFileLocator.locate(TypeDescription.ForLoadedType.getName(type)).resolve()));
}
apply(instrumentation, classDefinitions);
}
},
/**
* <p>
* Redefines a class using
* {@link java.lang.instrument.Instrumentation#retransformClasses(Class[])}. This requires synchronization on
* the {@link ClassReloadingStrategy#instrumentation} object.
* </p>
* <p>
* This strategy can require more time to be applied but does not struggle to reset VM anonymous classes (e.g. classes
* that represent lambda expressions).
* </p>
*/
RETRANSFORMATION(false) {
@Override
protected void apply(Instrumentation instrumentation, Map<Class<?>, ClassDefinition> classDefinitions) throws UnmodifiableClassException {
ClassRedefinitionTransformer classRedefinitionTransformer = new ClassRedefinitionTransformer(classDefinitions);
synchronized (this) {
instrumentation.addTransformer(classRedefinitionTransformer, REDEFINE_CLASSES);
try {
instrumentation.retransformClasses(classDefinitions.keySet().toArray(new Class<?>[classDefinitions.size()]));
} finally {
instrumentation.removeTransformer(classRedefinitionTransformer);
}
}
classRedefinitionTransformer.assertTransformation();
}
@Override
protected Strategy validate(Instrumentation instrumentation) {
if (!instrumentation.isRetransformClassesSupported()) {
throw new IllegalArgumentException("Does not support retransformation: " + instrumentation);
}
return this;
}
@Override
public void reset(Instrumentation instrumentation, ClassFileLocator classFileLocator, List<Class<?>> types) throws IOException, UnmodifiableClassException, ClassNotFoundException {
instrumentation.addTransformer(ClassResettingTransformer.INSTANCE, true);
try {
instrumentation.retransformClasses(types.toArray(new Class<?>[types.size()]));
} finally {
instrumentation.removeTransformer(ClassResettingTransformer.INSTANCE);
}
}
};
/**
* Indicates that a class is not redefined.
*/
private static final byte[] NO_REDEFINITION = null;
/**
* Instructs a {@link java.lang.instrument.ClassFileTransformer} to redefine classes.
*/
private static final boolean REDEFINE_CLASSES = true;
/**
* {@code true} if the {@link Strategy#REDEFINITION} strategy
* is used.
*/
private final boolean redefinition;
/**
* Creates a new strategy.
*
* @param redefinition {@code true} if the {@link Strategy#REDEFINITION} strategy is used.
*/
Strategy(boolean redefinition) {
this.redefinition = redefinition;
}
/**
* Applies this strategy for the given arguments.
*
* @param instrumentation The instrumentation to be used for applying the redefinition.
* @param classDefinitions A mapping of the classes to be redefined to their redefinition.
* @throws UnmodifiableClassException If a class is not modifiable.
* @throws ClassNotFoundException If a class was not found.
*/
protected abstract void apply(Instrumentation instrumentation, Map<Class<?>, ClassDefinition> classDefinitions) throws UnmodifiableClassException, ClassNotFoundException;
/**
* Validates that this strategy supports a given transformation type.
*
* @param instrumentation The instrumentation instance being used.
* @return This strategy.
*/
protected abstract Strategy validate(Instrumentation instrumentation);
/**
* Returns {@code true} if this strategy represents {@link Strategy#REDEFINITION}.
*
* @return {@code true} if this strategy represents {@link Strategy#REDEFINITION}.
*/
public boolean isRedefinition() {
return redefinition;
}
/**
* Resets the provided types to their original format.
*
* @param instrumentation The instrumentation instance to use for class redefinition or retransformation.
* @param classFileLocator The class file locator to use.
* @param types The types to reset.
* @throws IOException If an I/O exception occurs.
* @throws UnmodifiableClassException If a class is not modifiable.
* @throws ClassNotFoundException If a class could not be found.
*/
public abstract void reset(Instrumentation instrumentation, ClassFileLocator classFileLocator, List<Class<?>> types) throws IOException, UnmodifiableClassException, ClassNotFoundException;
/**
* A class file transformer that applies a given {@link java.lang.instrument.ClassDefinition}.
*/
protected static class ClassRedefinitionTransformer implements ClassFileTransformer {
/**
* A mapping of classes to be redefined to their redefined class definitions.
*/
private final Map<Class<?>, ClassDefinition> redefinedClasses;
/**
* Creates a new class redefinition transformer.
*
* @param redefinedClasses A mapping of classes to be redefined to their redefined class definitions.
*/
protected ClassRedefinitionTransformer(Map<Class<?>, ClassDefinition> redefinedClasses) {
this.redefinedClasses = redefinedClasses;
}
@Override
public byte[] transform(ClassLoader classLoader, String internalTypeName, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
if (internalTypeName == null) {
return NO_REDEFINITION;
}
ClassDefinition redefinedClass = redefinedClasses.remove(classBeingRedefined);
return redefinedClass == null ? NO_REDEFINITION : redefinedClass.getDefinitionClassFile();
}
/**
* Validates that all given classes were redefined.
*/
public void assertTransformation() {
if (!redefinedClasses.isEmpty()) {
throw new IllegalStateException("Could not transform: " + redefinedClasses.keySet());
}
}
}
/**
* A transformer that indicates that a class file should not be transformed.
*/
protected enum ClassResettingTransformer implements ClassFileTransformer {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public byte[] transform(ClassLoader classLoader, String internalTypeName, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
return NO_REDEFINITION;
}
}
}
/**
* A strategy to apply for injecting classes into the bootstrap class loader.
*/
protected interface BootstrapInjection {
/**
* Creates a class injector to use.
*
* @param instrumentation The instrumentation of this instance.
* @return A class injector for the bootstrap class loader.
*/
ClassInjector make(Instrumentation instrumentation);
/**
* A disabled bootstrap injection strategy.
*/
enum Disabled implements BootstrapInjection {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public ClassInjector make(Instrumentation instrumentation) {
throw new IllegalStateException("Bootstrap injection is not enabled");
}
}
/**
* An enabled bootstrap class loader injection strategy.
*/
class Enabled implements BootstrapInjection {
/**
* The folder to save jar files in.
*/
private final File folder;
/**
* Creates an enabled bootstrap class injection strategy.
*
* @param folder The folder to save jar files in.
*/
protected Enabled(File folder) {
this.folder = folder;
}
@Override
public ClassInjector make(Instrumentation instrumentation) {
return ClassInjector.UsingInstrumentation.of(folder, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, instrumentation);
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
public boolean equals(final java.lang.Object o) {
if (o == this) return true;
if (!(o instanceof ClassReloadingStrategy.BootstrapInjection.Enabled)) return false;
final ClassReloadingStrategy.BootstrapInjection.Enabled other = (ClassReloadingStrategy.BootstrapInjection.Enabled) o;
if (!other.canEqual((java.lang.Object) this)) return false;
final java.lang.Object this$folder = this.folder;
final java.lang.Object other$folder = other.folder;
if (this$folder == null ? other$folder != null : !this$folder.equals(other$folder)) return false;
return true;
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
protected boolean canEqual(final java.lang.Object other) {
return other instanceof ClassReloadingStrategy.BootstrapInjection.Enabled;
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
public int hashCode() {
final int PRIME = 59;
int result = 1;
final java.lang.Object $folder = this.folder;
result = result * PRIME + ($folder == null ? 43 : $folder.hashCode());
return result;
}
}
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
public boolean equals(final java.lang.Object o) {
if (o == this) return true;
if (!(o instanceof ClassReloadingStrategy)) return false;
final ClassReloadingStrategy other = (ClassReloadingStrategy) o;
if (!other.canEqual((java.lang.Object) this)) return false;
final java.lang.Object this$instrumentation = this.instrumentation;
final java.lang.Object other$instrumentation = other.instrumentation;
if (this$instrumentation == null ? other$instrumentation != null : !this$instrumentation.equals(other$instrumentation)) return false;
final java.lang.Object this$strategy = this.strategy;
final java.lang.Object other$strategy = other.strategy;
if (this$strategy == null ? other$strategy != null : !this$strategy.equals(other$strategy)) return false;
final java.lang.Object this$bootstrapInjection = this.bootstrapInjection;
final java.lang.Object other$bootstrapInjection = other.bootstrapInjection;
if (this$bootstrapInjection == null ? other$bootstrapInjection != null : !this$bootstrapInjection.equals(other$bootstrapInjection)) return false;
final java.lang.Object this$preregisteredTypes = this.preregisteredTypes;
final java.lang.Object other$preregisteredTypes = other.preregisteredTypes;
if (this$preregisteredTypes == null ? other$preregisteredTypes != null : !this$preregisteredTypes.equals(other$preregisteredTypes)) return false;
return true;
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
protected boolean canEqual(final java.lang.Object other) {
return other instanceof ClassReloadingStrategy;
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
public int hashCode() {
final int PRIME = 59;
int result = 1;
final java.lang.Object $instrumentation = this.instrumentation;
result = result * PRIME + ($instrumentation == null ? 43 : $instrumentation.hashCode());
final java.lang.Object $strategy = this.strategy;
result = result * PRIME + ($strategy == null ? 43 : $strategy.hashCode());
final java.lang.Object $bootstrapInjection = this.bootstrapInjection;
result = result * PRIME + ($bootstrapInjection == null ? 43 : $bootstrapInjection.hashCode());
final java.lang.Object $preregisteredTypes = this.preregisteredTypes;
result = result * PRIME + ($preregisteredTypes == null ? 43 : $preregisteredTypes.hashCode());
return result;
}
}