// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0
package net.orfjackal.retrolambda.lambdas;
import org.objectweb.asm.*;
import java.lang.invoke.*;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.concurrent.*;
public class LambdaReifier {
// These globals are used for communicating with the Java agent which
// is spying on the LambdaMetafactory's dynamically generated bytecode.
// We expect only one class being processed at a time, so it should
// be an error if these collections contain more than one element.
private static final BlockingDeque<Handle> currentLambdaImplMethod = new LinkedBlockingDeque<>(1);
private static final BlockingDeque<Handle> currentLambdaAccessMethod = new LinkedBlockingDeque<>(1);
private static final BlockingDeque<Class<?>> currentInvoker = new LinkedBlockingDeque<>(1);
private static final BlockingDeque<Type> currentInvokedType = new LinkedBlockingDeque<>(1);
private static final BlockingDeque<String> currentLambdaClass = new LinkedBlockingDeque<>(1);
public static LambdaFactoryMethod reifyLambdaClass(Handle lambdaImplMethod, Handle lambdaAccessMethod,
Class<?> invoker, String invokedName, Type invokedType, Handle bsm, Object[] bsmArgs) {
try {
setLambdaImplMethod(lambdaImplMethod);
setLambdaAccessMethod(lambdaAccessMethod);
setInvoker(invoker);
setInvokedType(invokedType);
// Causes the lambda class to be loaded. Retrolambda's Java agent
// will detect it, save it to a file and tell us (via the globals
// in this class) that what the name of the lambda class was.
callBootstrapMethod(invoker, invokedName, invokedType, bsm, bsmArgs);
return getLambdaFactoryMethod();
} catch (Throwable t) {
throw new RuntimeException("Failed to backport lambda or method reference: " + lambdaImplMethod, t);
} finally {
resetGlobals();
}
}
private static void setLambdaImplMethod(Handle lambdaImplMethod) {
currentLambdaImplMethod.push(lambdaImplMethod);
}
private static void setLambdaAccessMethod(Handle lambdaAccessMethod) {
currentLambdaAccessMethod.push(lambdaAccessMethod);
}
private static void setInvoker(Class<?> lambdaInvoker) {
currentInvoker.push(lambdaInvoker);
}
private static void setInvokedType(Type invokedType) {
currentInvokedType.push(invokedType);
}
public static void setLambdaClass(String lambdaClass) {
currentLambdaClass.push(lambdaClass);
}
public static boolean isLambdaClassToReify(String className) {
Class<?> invoker = currentInvoker.peekFirst();
return invoker != null
&& className.startsWith(Type.getInternalName(invoker))
&& LambdaNaming.LAMBDA_CLASS.matcher(className).matches();
}
public static Handle getLambdaImplMethod() {
return currentLambdaImplMethod.getFirst();
}
public static Handle getLambdaAccessMethod() {
return currentLambdaAccessMethod.getFirst();
}
public static LambdaFactoryMethod getLambdaFactoryMethod() {
String lambdaClass = currentLambdaClass.getFirst();
Type invokedType = currentInvokedType.getFirst();
return new LambdaFactoryMethod(lambdaClass, invokedType);
}
private static void resetGlobals() {
currentLambdaImplMethod.clear();
currentLambdaAccessMethod.clear();
currentInvoker.clear();
currentInvokedType.clear();
currentLambdaClass.clear();
}
private static CallSite callBootstrapMethod(Class<?> invoker, String invokedName, Type invokedType, Handle bsm, Object[] bsmArgs) throws Throwable {
ClassLoader cl = invoker.getClassLoader();
MethodHandles.Lookup caller = getLookup(invoker);
List<Object> args = new ArrayList<>();
args.add(caller);
args.add(invokedName);
args.add(Types.toMethodType(invokedType, cl));
for (Object arg : bsmArgs) {
args.add(Types.asmToJdkType(arg, cl, caller));
}
MethodHandle bootstrapMethod = Types.toMethodHandle(bsm, cl, caller);
return (CallSite) bootstrapMethod.invokeWithArguments(args);
}
private static MethodHandles.Lookup getLookup(Class<?> targetClass) throws Exception {
Constructor<MethodHandles.Lookup> ctor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);
ctor.setAccessible(true);
return ctor.newInstance(targetClass);
}
}