org.testifyproject.bytebuddy.agent.builder.LambdaFactory Maven / Gradle / Ivy
The newest version!
package org.testifyproject.bytebuddy.agent.builder;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.EqualsAndHashCode;
import org.testifyproject.bytebuddy.description.type.TypeDescription;
import org.testifyproject.bytebuddy.dynamic.ClassFileLocator;
import org.testifyproject.bytebuddy.dynamic.loading.ClassInjector;
import java.lang.instrument.ClassFileTransformer;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* This class serves as a dispatcher for creating lambda expression objects when Byte Buddy is configured to instrument the
* {@code java.lang.invoke.LambdaMetafactory}. For this purpose, this class is injected into the class path to serve as a VM-global
* singleton and for becoming reachable from the JVM's meta factory. This class keeps a reference to all registered transformers which need
* to be explicitly deregistered in order to avoid a memory leak.
*/
@EqualsAndHashCode
public class LambdaFactory {
/**
* The name of the field to access.
*/
private static final String FIELD_NAME = "CLASS_FILE_TRANSFORMERS";
/**
* A mapping of all registered class file transformers and their lambda factories, linked in their application order.
* This field must not be accessed directly but only by reading this class from the system class loader.
*/
@SuppressFBWarnings(value = "MS_MUTABLE_COLLECTION_PKGPROTECT", justification = "The field must be accessible by different class loader instances")
public static final Map CLASS_FILE_TRANSFORMERS = new ConcurrentHashMap();
/**
* The target instance that is a factory for creating lambdas.
*/
private final Object target;
/**
* The dispatcher method to invoke for creating a new lambda instance.
*/
private final Method dispatcher;
/**
* Creates a new lambda factory.
*
* @param target The target instance that is a factory for creating lambdas.
* @param dispatcher The dispatcher method to invoke for creating a new lambda instance.
*/
public LambdaFactory(Object target, Method dispatcher) {
this.target = target;
this.dispatcher = dispatcher;
}
/**
* Registers a class file transformer together with a factory for creating a lambda expression. It is possible to call this method independently
* of the class loader's context as the supplied injector makes sure that the manipulated collection is the one that is held by the system class
* loader.
*
* @param classFileTransformer The class file transformer to register.
* @param classFileFactory The lambda class file factory to use. This factory must define a visible instance method with the signature
* {@code byte[] make(Object, String, Object, Object, Object, Object, boolean, List, List, Collection}. The arguments provided
* are the invokedynamic call site's lookup object, the lambda method's name, the factory method's type, the lambda method's
* type, the target method's handle, the specialized method type of the lambda expression, a boolean to indicate
* serializability, a list of marker interfaces, a list of additional bridges and a collection of class file transformers to
* apply.
* @return {@code true} if this is the first registered transformer. This indicates that the {@code LambdaMetafactory} must be instrumented to delegate
* to this alternative factory.
*/
@SuppressWarnings("all")
public static boolean register(ClassFileTransformer classFileTransformer, Object classFileFactory) {
try {
TypeDescription typeDescription = new TypeDescription.ForLoadedType(LambdaFactory.class);
Class> lambdaFactory = ClassInjector.UsingReflection.ofSystemClassLoader()
.inject(Collections.singletonMap(typeDescription, ClassFileLocator.ForClassLoader.read(LambdaFactory.class).resolve()))
.get(typeDescription);
@SuppressWarnings("unchecked")
Map classFileTransformers = (Map) lambdaFactory
.getField(FIELD_NAME)
.get(null);
synchronized (classFileTransformers) {
try {
return classFileTransformers.isEmpty();
} finally {
classFileTransformers.put(classFileTransformer, lambdaFactory
.getConstructor(Object.class, Method.class)
.newInstance(classFileFactory, classFileFactory.getClass().getMethod("make",
Object.class,
String.class,
Object.class,
Object.class,
Object.class,
Object.class,
boolean.class,
List.class,
List.class,
Collection.class)));
}
}
} catch (RuntimeException exception) {
throw exception;
} catch (Exception exception) {
throw new IllegalStateException("Could not register class file transformer", exception);
}
}
/**
* Releases a class file transformer.
*
* @param classFileTransformer The class file transformer to release.
* @return {@code true} if the removed transformer was the last class file transformer registered. This indicates that the {@code LambdaMetafactory} must
* be instrumented to no longer delegate to this alternative factory.
*/
@SuppressWarnings("all")
public static boolean release(ClassFileTransformer classFileTransformer) {
try {
@SuppressWarnings("unchecked")
Map classFileTransformers = (Map) ClassLoader.getSystemClassLoader()
.loadClass(LambdaFactory.class.getName())
.getField(FIELD_NAME)
.get(null);
synchronized (classFileTransformers) {
return classFileTransformers.remove(classFileTransformer) != null && classFileTransformers.isEmpty();
}
} catch (RuntimeException exception) {
throw exception;
} catch (Exception exception) {
throw new IllegalStateException("Could not release class file transformer", exception);
}
}
/**
* Applies this lambda meta factory.
*
* @param caller A lookup context representing the creating class of this lambda expression.
* @param invokedName The name of the lambda expression's represented method.
* @param invokedType The type of the lambda expression's factory method.
* @param samMethodType The type of the lambda expression's represented method.
* @param implMethod A handle representing the target of the lambda expression's method.
* @param instantiatedMethodType A specialization of the type of the lambda expression's represented method.
* @param serializable {@code true} if the lambda expression should be serializable.
* @param markerInterfaces A list of interfaces for the lambda expression to represent.
* @param additionalBridges A list of additional bridge methods to be implemented by the lambda expression.
* @param classFileTransformers A collection of class file transformers to apply when creating the class.
* @return A binary representation of the transformed class file.
*/
private byte[] invoke(Object caller,
String invokedName,
Object invokedType,
Object samMethodType,
Object implMethod,
Object instantiatedMethodType,
boolean serializable,
List> markerInterfaces,
List> additionalBridges,
Collection classFileTransformers) {
try {
return (byte[]) dispatcher.invoke(target,
caller,
invokedName,
invokedType,
samMethodType,
implMethod,
instantiatedMethodType,
serializable,
markerInterfaces,
additionalBridges,
classFileTransformers);
} catch (RuntimeException exception) {
throw exception;
} catch (Exception exception) {
throw new IllegalStateException("Cannot create class for lambda expression", exception);
}
}
/**
* Dispatches the creation of a new class representing a class file.
*
* @param caller A lookup context representing the creating class of this lambda expression.
* @param invokedName The name of the lambda expression's represented method.
* @param invokedType The type of the lambda expression's factory method.
* @param samMethodType The type of the lambda expression's represented method.
* @param implMethod A handle representing the target of the lambda expression's method.
* @param instantiatedMethodType A specialization of the type of the lambda expression's represented method.
* @param serializable {@code true} if the lambda expression should be serializable.
* @param markerInterfaces A list of interfaces for the lambda expression to represent.
* @param additionalBridges A list of additional bridge methods to be implemented by the lambda expression.
* @return A binary representation of the transformed class file.
*/
public static byte[] make(Object caller,
String invokedName,
Object invokedType,
Object samMethodType,
Object implMethod,
Object instantiatedMethodType,
boolean serializable,
List> markerInterfaces,
List> additionalBridges) {
return CLASS_FILE_TRANSFORMERS.values().iterator().next().invoke(caller,
invokedName,
invokedType,
samMethodType,
implMethod,
instantiatedMethodType,
serializable,
markerInterfaces,
additionalBridges,
CLASS_FILE_TRANSFORMERS.keySet());
}
}