package org.rakam.server.http.util;
import com.google.common.base.Throwables;
import org.rakam.server.http.HttpService;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import static java.lang.invoke.MethodHandles.lookup;
public class Lambda {
private final static Method biConsumerAccept;
private final static Method consumerAccept;
private final static Method functionApply;
private final static Method biFunctionApply;
static {
try {
biConsumerAccept = BiConsumer.class.getMethod("accept", Object.class, Object.class);
consumerAccept = Consumer.class.getMethod("accept", Object.class);
functionApply = Function.class.getMethod("apply", Object.class);
biFunctionApply = BiFunction.class.getMethod("apply", Object.class, Object.class);
} catch (NoSuchMethodException e) {
throw Throwables.propagate(e);
}
}
public static <T> T produceLambdaForConsumer(final Method sourceMethod) {
return produceLambda(sourceMethod, consumerAccept);
}
public static BiFunction produceLambdaForBiFunction(Method method) {
return produceLambda(method, biFunctionApply);
}
public static BiConsumer produceLambdaForBiConsumer(final Method sourceMethod) {
return produceLambda(sourceMethod, biConsumerAccept);
}
public static Function produceLambdaForFunction(final Method sourceMethod) {
return produceLambda(sourceMethod, functionApply);
}
public static <T> T produceLambda(final Method sourceMethod, final Method targetMethod) {
MethodHandles.Lookup lookup = lookup();
sourceMethod.setAccessible(true);
final MethodHandles.Lookup caller = lookup.in(sourceMethod.getDeclaringClass());
final MethodHandle implementationMethod;
try {
implementationMethod = caller.unreflect(sourceMethod);
} catch (IllegalAccessException e) {
throw Throwables.propagate(e);
}
final MethodType factoryMethodType = MethodType.methodType(targetMethod.getDeclaringClass());
final Class<?> methodReturn = targetMethod.getReturnType();
final Class<?>[] methodParams = targetMethod.getParameterTypes();
final MethodType functionMethodType = MethodType.methodType(methodReturn, methodParams);
final CallSite lambdaFactory;
try {
lambdaFactory = LambdaMetafactory.metafactory(
lookup,
targetMethod.getName(),
factoryMethodType,
functionMethodType,
implementationMethod,
implementationMethod.type()
);
final MethodHandle factoryInvoker = lambdaFactory.getTarget();
return (T) factoryInvoker.invoke();
} catch (Throwable e) {
// TODO: fallback to classic reflection method if lambda generation fails.
throw new InternalError(String.format("Unable to generate lambda for method %s. %s",
sourceMethod.getDeclaringClass().getName() + "." + sourceMethod.getName(),
e.getMessage()));
}
}
}