com.github.mustachejava.indy.IndyWrapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of indy Show documentation
Show all versions of indy Show documentation
Invokedynamic implementation of ObjectHandler
package com.github.mustachejava.indy;
import com.github.mustachejava.MustacheException;
import com.github.mustachejava.codegen.CodegenReflectionWrapper;
import com.github.mustachejava.reflect.ReflectionWrapper;
import com.github.mustachejava.util.GuardException;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.GeneratorAdapter;
import java.lang.invoke.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* Creates wrappers using ASM and Invokedynamic.
*/
public abstract class IndyWrapper extends CodegenReflectionWrapper implements Opcodes {
public static final Handle BOOTSTRAP_METHOD =
new Handle(Opcodes.H_INVOKESTATIC, "com/github/mustachejava/indy/IndyWrapper",
"bootstrap",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
MethodType.class).toMethodDescriptorString());
private static final String METHOD_SIGNATURE =
"(Lcom/github/mustachejava/indy/IndyWrapper;Ljava/lang/Object;)Ljava/lang/Object;";
/**
* This bootstrap method simply points to the lookup method so we can see what is on the stack at runtime
* since we need the parameters in order to make a decision.
*
* @param caller
* @param name
* @param type
* @return
* @throws NoSuchMethodException
* @throws IllegalAccessException
*/
public static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
MutableCallSite callSite = new MutableCallSite(MethodType.methodType(Object.class, IndyWrapper.class, Object.class));
MethodHandle lookup = caller.findStatic(IndyWrapper.class, "lookup",
MethodType.methodType(Object.class, MutableCallSite.class, IndyWrapper.class,
Object.class));
lookup = MethodHandles.insertArguments(lookup, 0, callSite);
callSite.setTarget(lookup);
return callSite;
}
protected IndyWrapper(ReflectionWrapper rw) {
super(rw);
}
public abstract Object call(Object[] scopes) throws GuardException; // {
public static IndyWrapper create(CodegenReflectionWrapper rw) {
return create(rw, true);
}
public static IndyWrapper create(CodegenReflectionWrapper rw, boolean guard) {
String name;
Method method = rw.getMethod();
if (method == null) {
Field field = rw.getField();
name = "W_" + field.getDeclaringClass().getSimpleName() + "_" + field.getName();
} else {
name = "W_" + method.getDeclaringClass().getSimpleName() + "_" + method.getName();
}
String className = encodeClassName("com.github.mustachejava.indy", name);
try {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, className.replace(".", "/"), null, "com/github/mustachejava/indy/IndyWrapper", null);
cw.visitSource(className + ".java", null);
{
org.objectweb.asm.commons.Method constructor = org.objectweb.asm.commons.Method.getMethod(
"void (com.github.mustachejava.reflect.ReflectionWrapper)");
GeneratorAdapter ga = new GeneratorAdapter(ACC_PUBLIC, constructor, null, null, cw);
ga.loadThis();
ga.loadArg(0);
ga.invokeConstructor(Type.getType(IndyWrapper.class), constructor);
ga.returnValue();
ga.endMethod();
}
{
GeneratorAdapter ga = new GeneratorAdapter(ACC_PUBLIC,
org.objectweb.asm.commons.Method.getMethod("Object call(Object[])"), null,
new Type[] { Type.getType(GuardException.class)}, cw);
if (guard) {
ga.visitVarInsn(ALOAD, 0);
ga.visitVarInsn(ALOAD, 1);
ga.invokeVirtual(Type.getType(IndyWrapper.class),
org.objectweb.asm.commons.Method.getMethod("void guardCall(Object[])"));
}
ga.visitVarInsn(ALOAD, 0);
ga.visitVarInsn(ALOAD, 1);
ga.invokeVirtual(Type.getType(IndyWrapper.class),
org.objectweb.asm.commons.Method.getMethod("Object unwrap(Object[])"));
ga.visitVarInsn(ASTORE, 2);
ga.visitVarInsn(ALOAD, 2);
Label l0 = new Label();
ga.ifNonNull(l0);
ga.visitInsn(ACONST_NULL);
ga.returnValue();
ga.visitLabel(l0);
ga.visitVarInsn(ALOAD, 0);
ga.visitVarInsn(ALOAD, 2);
ga.invokeDynamic("bootstrap", METHOD_SIGNATURE, BOOTSTRAP_METHOD);
ga.returnValue();
ga.endMethod();
}
cw.visitEnd();
Class> aClass = defineClass(className, cw.toByteArray());
return (IndyWrapper) aClass.getConstructor(ReflectionWrapper.class).newInstance(rw);
} catch (Exception e) {
throw new MustacheException(e);
}
}
public static String encodeClassName(String pkgName, String name) {
String uuid = UUID.randomUUID().toString().replace("-", "_");
return pkgName + "." + name + "_" + uuid;
}
private static final IndyClassLoader indyCL = new IndyClassLoader(Thread.currentThread().getContextClassLoader());
private static class IndyClassLoader extends ClassLoader {
public IndyClassLoader(ClassLoader parent) {
super(parent);
}
public Class> defineClass(final String name, final byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
public static Class> defineClass(String name, byte[] b) {
return indyCL.defineClass(name, b);
}
/**
* This bootstrap method does the actual work of tracking down the CallSite at runtime.
*
* @param callSite
* @param iw
* @param scope
* @return
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public static Object lookup(MutableCallSite callSite, IndyWrapper iw, Object scope) throws IllegalAccessException, InvocationTargetException {
Method method = iw.getMethod();
if (method == null) {
Field field = iw.getField();
MethodHandle unreflect = MethodHandles.lookup().unreflectGetter(field);
unreflect = MethodHandles.dropArguments(unreflect, 0, IndyWrapper.class);
setCallsite(callSite, unreflect);
return field.get(scope);
} else {
MethodHandle unreflect = MethodHandles.lookup().unreflect(method);
unreflect = MethodHandles.dropArguments(unreflect, 0, IndyWrapper.class);
if (method.getParameterTypes().length != 0) {
for (int i = 0; i < iw.getArguments().length; i++) {
unreflect = MethodHandles.insertArguments(unreflect, i + 2, iw.getArguments()[i]);
}
}
setCallsite(callSite, unreflect);
return method.invoke(scope, iw.getArguments());
}
}
private static void setCallsite(MutableCallSite callSite, MethodHandle unreflect) {
unreflect = MethodHandles.explicitCastArguments(unreflect,
MethodType.methodType(Object.class, IndyWrapper.class, Object.class));
callSite.setTarget(unreflect);
}
}