# HG changeset patch # User Jaroslav Tulach # Date 1383747354 -3600 # Node ID 86aabecda7a373dadbed93e6e4793c65e8fe9e9f # Parent 4a93f2679691d4ae119a4860f96a4c7226bb0a40 Introducing Agent-Class to allow java.lang.instrument-like transforms of the @JavaScriptBody annotation diff -r 4a93f2679691 -r 86aabecda7a3 boot/pom.xml --- a/boot/pom.xml Tue Nov 05 23:06:32 2013 +0100 +++ b/boot/pom.xml Wed Nov 06 15:15:54 2013 +0100 @@ -21,6 +21,11 @@ org.apache.felix maven-bundle-plugin + + + org.apidesign.html.boot.impl.JsAgent + + org.apache.maven.plugins diff -r 4a93f2679691 -r 86aabecda7a3 boot/src/main/java/org/apidesign/html/boot/impl/FnContext.java --- a/boot/src/main/java/org/apidesign/html/boot/impl/FnContext.java Tue Nov 05 23:06:32 2013 +0100 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/FnContext.java Wed Nov 06 15:15:54 2013 +0100 @@ -32,16 +32,16 @@ public final class FnContext implements Closeable { private static final Logger LOG = Logger.getLogger(FnContext.class.getName()); - private Fn.Presenter prev; + private Object prev; private FnContext(Fn.Presenter p) { this.prev = p; } @Override public void close() throws IOException { - if (prev != null) { - currentPresenter(prev); - prev = null; + if (prev != this) { + currentPresenter((Fn.Presenter)prev); + prev = this; } } /* diff -r 4a93f2679691 -r 86aabecda7a3 boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java --- a/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java Tue Nov 05 23:06:32 2013 +0100 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java Wed Nov 06 15:15:54 2013 +0100 @@ -20,6 +20,7 @@ */ package org.apidesign.html.boot.impl; +import java.io.Closeable; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; @@ -28,23 +29,41 @@ import java.util.Collections; import java.util.Enumeration; import java.util.List; +import java.util.concurrent.Callable; import org.apidesign.html.boot.spi.Fn; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.objectweb.asm.signature.SignatureWriter; /** * * @author Jaroslav Tulach */ -public final class FnUtils { +public final class FnUtils implements Fn.Presenter { private FnUtils() { } - public static Fn define(Class caller, String code, String... names) { - return FnContext.currentPresenter().defineFn(code, names); - } - public static boolean isJavaScriptCapable(ClassLoader l) { - return l instanceof JsClassLoader; + if (l instanceof JsClassLoader) { + return true; + } + Class clazz; + try (Closeable c = Fn.activate(new FnUtils())) { + clazz = Class.forName(Test.class.getName(), true, l); + final Object is = ((Callable)clazz.newInstance()).call(); + return Boolean.TRUE.equals(is); + } catch (Exception ex) { + return false; + } } public static boolean isValid(Fn fn) { @@ -100,7 +119,7 @@ }.parse(body); } - static void loadScript(JsClassLoader jcl, String resource) { + static void loadScript(ClassLoader jcl, String resource) { final InputStream script = jcl.getResourceAsStream(resource); if (script == null) { throw new NullPointerException("Can't find " + resource); @@ -109,7 +128,7 @@ Reader isr = null; try { isr = new InputStreamReader(script, "UTF-8"); - jcl.loadScript(isr); + FnContext.currentPresenter().loadScript(isr); } finally { if (isr != null) { isr.close(); @@ -119,4 +138,429 @@ throw new IllegalStateException("Can't execute " + resource, ex); } } + + @Override + public Fn defineFn(String code, String... names) { + return new TrueFn(); + } + + @Override + public void displayPage(URL page, Runnable onPageLoad) { + } + + @Override + public void loadScript(Reader code) throws Exception { + } + + private static final class FindInClass extends ClassVisitor { + private String name; + private int found; + private ClassLoader loader; + + public FindInClass(ClassLoader l, ClassVisitor cv) { + super(Opcodes.ASM4, cv); + this.loader = l; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.name = name; + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) { + return new LoadResource(); + } + return super.visitAnnotation(desc, visible); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return new FindInMethod(access, name, desc, + super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions) + ); + } + + private final class FindInMethod extends MethodVisitor { + + private final String name; + private final String desc; + private final int access; + private List args; + private String body; + private boolean bodyGenerated; + + public FindInMethod(int access, String name, String desc, MethodVisitor mv) { + super(Opcodes.ASM4, mv); + this.access = access; + this.name = name; + this.desc = desc; + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if ("Lnet/java/html/js/JavaScriptBody;".equals(desc) // NOI18N + || "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;".equals(desc) // NOI18N + ) { + found++; + return new FindInAnno(); + } + return super.visitAnnotation(desc, visible); + } + + private void generateJSBody(List args, String body) { + this.args = args; + this.body = body; + } + + @Override + public void visitCode() { + if (body == null) { + return; + } + generateBody(); + } + + private boolean generateBody() { + if (bodyGenerated) { + return false; + } + bodyGenerated = true; + + super.visitFieldInsn( + Opcodes.GETSTATIC, FindInClass.this.name, + "$$fn$$" + name + "_" + found, + "Lorg/apidesign/html/boot/spi/Fn;" + ); + super.visitInsn(Opcodes.DUP); + super.visitMethodInsn( + Opcodes.INVOKESTATIC, + "org/apidesign/html/boot/spi/Fn", "isValid", + "(Lorg/apidesign/html/boot/spi/Fn;)Z" + ); + Label ifNotNull = new Label(); + super.visitJumpInsn(Opcodes.IFNE, ifNotNull); + + // init Fn + super.visitInsn(Opcodes.POP); + super.visitLdcInsn(Type.getObjectType(FindInClass.this.name)); + super.visitLdcInsn(body); + super.visitIntInsn(Opcodes.SIPUSH, args.size()); + super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String"); + boolean needsVM = false; + for (int i = 0; i < args.size(); i++) { + assert !needsVM; + String argName = args.get(i); + needsVM = "vm".equals(argName); + super.visitInsn(Opcodes.DUP); + super.visitIntInsn(Opcodes.BIPUSH, i); + super.visitLdcInsn(argName); + super.visitInsn(Opcodes.AASTORE); + } + super.visitMethodInsn(Opcodes.INVOKESTATIC, + "org/apidesign/html/boot/spi/Fn", "define", + "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;" + ); + super.visitInsn(Opcodes.DUP); + super.visitFieldInsn( + Opcodes.PUTSTATIC, FindInClass.this.name, + "$$fn$$" + name + "_" + found, + "Lorg/apidesign/html/boot/spi/Fn;" + ); + // end of Fn init + + super.visitLabel(ifNotNull); + + final int offset; + if ((access & Opcodes.ACC_STATIC) == 0) { + offset = 1; + super.visitIntInsn(Opcodes.ALOAD, 0); + } else { + offset = 0; + super.visitInsn(Opcodes.ACONST_NULL); + } + + super.visitIntInsn(Opcodes.SIPUSH, args.size()); + super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + + class SV extends SignatureVisitor { + + private boolean nowReturn; + private Type returnType; + private int index; + private int loadIndex = offset; + + public SV() { + super(Opcodes.ASM4); + } + + @Override + public void visitBaseType(char descriptor) { + final Type t = Type.getType("" + descriptor); + if (nowReturn) { + returnType = t; + return; + } + FindInMethod.super.visitInsn(Opcodes.DUP); + FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++); + FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++); + String factory; + switch (descriptor) { + case 'I': + factory = "java/lang/Integer"; + break; + case 'J': + factory = "java/lang/Long"; + loadIndex++; + break; + case 'S': + factory = "java/lang/Short"; + break; + case 'F': + factory = "java/lang/Float"; + break; + case 'D': + factory = "java/lang/Double"; + loadIndex++; + break; + case 'Z': + factory = "java/lang/Boolean"; + break; + case 'C': + factory = "java/lang/Character"; + break; + case 'B': + factory = "java/lang/Byte"; + break; + default: + throw new IllegalStateException(t.toString()); + } + FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC, + factory, "valueOf", "(" + descriptor + ")L" + factory + ";" + ); + FindInMethod.super.visitInsn(Opcodes.AASTORE); + } + + @Override + public SignatureVisitor visitArrayType() { + if (nowReturn) { + throw new IllegalStateException("Not supported yet"); + } + loadObject(); + return new SignatureWriter(); + } + + @Override + public void visitClassType(String name) { + if (nowReturn) { + returnType = Type.getObjectType(name); + return; + } + loadObject(); + } + + @Override + public SignatureVisitor visitReturnType() { + nowReturn = true; + return this; + } + + private void loadObject() { + FindInMethod.super.visitInsn(Opcodes.DUP); + FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++); + FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++); + FindInMethod.super.visitInsn(Opcodes.AASTORE); + } + + } + SV sv = new SV(); + SignatureReader sr = new SignatureReader(desc); + sr.accept(sv); + + if (needsVM) { + FindInMethod.super.visitInsn(Opcodes.DUP); + FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index); + int lastSlash = FindInClass.this.name.lastIndexOf('/'); + String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$"; + FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";"); + FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";"); + FindInMethod.super.visitInsn(Opcodes.AASTORE); + } + + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;" + ); + switch (sv.returnType.getSort()) { + case Type.VOID: + super.visitInsn(Opcodes.RETURN); + break; + case Type.ARRAY: + case Type.OBJECT: + super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName()); + super.visitInsn(Opcodes.ARETURN); + break; + case Type.BOOLEAN: + super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean"); + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "java/lang/Boolean", "booleanValue", "()Z" + ); + super.visitInsn(Opcodes.IRETURN); + break; + default: + super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number"); + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor() + ); + super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN)); + } + return true; + } + + @Override + public void visitEnd() { + super.visitEnd(); + if (body != null) { + if (generateBody()) { + // native method + super.visitMaxs(1, 0); + } + FindInClass.this.visitField( + Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, + "$$fn$$" + name + "_" + found, + "Lorg/apidesign/html/boot/spi/Fn;", + null, null + ); + } + } + + private final class FindInAnno extends AnnotationVisitor { + + private List args = new ArrayList(); + private String body; + private boolean javacall = false; + + public FindInAnno() { + super(Opcodes.ASM4); + } + + @Override + public void visit(String name, Object value) { + if (name == null) { + args.add((String) value); + return; + } + if (name.equals("javacall")) { // NOI18N + javacall = (Boolean) value; + return; + } + assert name.equals("body"); + body = (String) value; + } + + @Override + public AnnotationVisitor visitArray(String name) { + return this; + } + + @Override + public void visitEnd() { + if (body != null) { + if (javacall) { + body = callback(body); + args.add("vm"); + } + generateJSBody(args, body); + } + } + } + } + + private final class LoadResource extends AnnotationVisitor { + + public LoadResource() { + super(Opcodes.ASM4); + } + + @Override + public void visit(String attrName, Object value) { + String relPath = (String) value; + if (relPath.startsWith("/")) { + loadScript(loader, relPath); + } else { + int last = name.lastIndexOf('/'); + String fullPath = name.substring(0, last + 1) + relPath; + loadScript(loader, fullPath); + } + } + } + } + + private static class ClassWriterEx extends ClassWriter { + + private ClassLoader loader; + + public ClassWriterEx(ClassLoader l, ClassReader classReader, int flags) { + super(classReader, flags); + this.loader = l; + } + + @Override + protected String getCommonSuperClass(final String type1, final String type2) { + Class c, d; + try { + c = Class.forName(type1.replace('/', '.'), false, loader); + d = Class.forName(type2.replace('/', '.'), false, loader); + } catch (Exception e) { + throw new RuntimeException(e.toString()); + } + if (c.isAssignableFrom(d)) { + return type1; + } + if (d.isAssignableFrom(c)) { + return type2; + } + if (c.isInterface() || d.isInterface()) { + return "java/lang/Object"; + } else { + do { + c = c.getSuperclass(); + } while (!c.isAssignableFrom(d)); + return c.getName().replace('.', '/'); + } + } + } + + static byte[] transform(ClassLoader loader, byte[] arr) { + ClassReader cr = new ClassReader(arr) { + // to allow us to compile with -profile compact1 on + // JDK8 while processing the class as JDK7, the highest + // class format asm 4.1 understands to + @Override + public short readShort(int index) { + short s = super.readShort(index); + if (index == 6 && s > Opcodes.V1_7) { + return Opcodes.V1_7; + } + return s; + } + }; + FindInClass tst = new FindInClass(loader, null); + cr.accept(tst, 0); + if (tst.found > 0) { + ClassWriter w = new ClassWriterEx(loader, cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + FindInClass fic = new FindInClass(loader, w); + cr.accept(fic, 0); + arr = w.toByteArray(); + } + return arr; + } + + private static final class TrueFn extends Fn { + @Override + public Object invoke(Object thiz, Object... args) throws Exception { + return Boolean.TRUE; + } + } // end of TrueFn } diff -r 4a93f2679691 -r 86aabecda7a3 boot/src/main/java/org/apidesign/html/boot/impl/JsAgent.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JsAgent.java Wed Nov 06 15:15:54 2013 +0100 @@ -0,0 +1,45 @@ +/** + * HTML via Java(tm) Language Bindings + * Copyright (C) 2013 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. apidesign.org + * designates this particular file as subject to the + * "Classpath" exception as provided by apidesign.org + * in the License file that accompanied this code. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException + */ +package org.apidesign.html.boot.impl; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; + +/** + * + * @author Jaroslav Tulach + */ +public final class JsAgent implements ClassFileTransformer { + public static void agentmain(String args, Instrumentation instr) { + instr.addTransformer(new JsAgent()); + } + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + try { + return FnUtils.transform(loader, classfileBuffer); + } catch (Exception ex) { + return classfileBuffer; + } + } +} diff -r 4a93f2679691 -r 86aabecda7a3 boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java --- a/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java Tue Nov 05 23:06:32 2013 +0100 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java Wed Nov 06 15:15:54 2013 +0100 @@ -25,20 +25,7 @@ import java.io.InputStream; import java.io.Reader; import java.net.URL; -import java.util.ArrayList; import java.util.Enumeration; -import java.util.List; -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.signature.SignatureReader; -import org.objectweb.asm.signature.SignatureVisitor; -import org.objectweb.asm.signature.SignatureWriter; /** * @@ -102,27 +89,7 @@ } is.close(); is = null; - ClassReader cr = new ClassReader(arr) { - // to allow us to compile with -profile compact1 on - // JDK8 while processing the class as JDK7, the highest - // class format asm 4.1 understands to - @Override - public short readShort(int index) { - short s = super.readShort(index); - if (index == 6 && s > Opcodes.V1_7) { - return Opcodes.V1_7; - } - return s; - } - }; - FindInClass tst = new FindInClass(null); - cr.accept(tst, 0); - if (tst.found > 0) { - ClassWriter w = new ClassWriterEx(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - FindInClass fic = new FindInClass(w); - cr.accept(fic, 0); - arr = w.toByteArray(); - } + arr = FnUtils.transform(JsClassLoader.this, arr); if (arr != null) { return defineClass(name, arr, 0, arr.length); } @@ -141,363 +108,4 @@ protected abstract Fn defineFn(String code, String... names); protected abstract void loadScript(Reader code) throws Exception; - - private final class FindInClass extends ClassVisitor { - private String name; - private int found; - - public FindInClass(ClassVisitor cv) { - super(Opcodes.ASM4, cv); - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - this.name = name; - super.visit(version, access, name, signature, superName, interfaces); - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) { - return new LoadResource(); - } - return super.visitAnnotation(desc, visible); - } - - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - return new FindInMethod(access, name, desc, - super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions) - ); - } - - private final class FindInMethod extends MethodVisitor { - private final String name; - private final String desc; - private final int access; - private List args; - private String body; - private boolean bodyGenerated; - - public FindInMethod(int access, String name, String desc, MethodVisitor mv) { - super(Opcodes.ASM4, mv); - this.access = access; - this.name = name; - this.desc = desc; - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - if ( - "Lnet/java/html/js/JavaScriptBody;".equals(desc) // NOI18N - || "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;".equals(desc) // NOI18N - ) { - found++; - return new FindInAnno(); - } - return super.visitAnnotation(desc, visible); - } - - private void generateJSBody(List args, String body) { - this.args = args; - this.body = body; - } - - @Override - public void visitCode() { - if (body == null) { - return; - } - generateBody(); - } - - private boolean generateBody() { - if (bodyGenerated) { - return false; - } - bodyGenerated = true; - - super.visitFieldInsn( - Opcodes.GETSTATIC, FindInClass.this.name, - "$$fn$$" + name + "_" + found, - "Lorg/apidesign/html/boot/spi/Fn;" - ); - super.visitInsn(Opcodes.DUP); - super.visitMethodInsn( - Opcodes.INVOKESTATIC, - "org/apidesign/html/boot/impl/FnUtils", "isValid", - "(Lorg/apidesign/html/boot/spi/Fn;)Z" - ); - Label ifNotNull = new Label(); - super.visitJumpInsn(Opcodes.IFNE, ifNotNull); - - // init Fn - super.visitInsn(Opcodes.POP); - super.visitLdcInsn(Type.getObjectType(FindInClass.this.name)); - super.visitLdcInsn(body); - super.visitIntInsn(Opcodes.SIPUSH, args.size()); - super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String"); - boolean needsVM = false; - for (int i = 0; i < args.size(); i++) { - assert !needsVM; - String argName = args.get(i); - needsVM = "vm".equals(argName); - super.visitInsn(Opcodes.DUP); - super.visitIntInsn(Opcodes.BIPUSH, i); - super.visitLdcInsn(argName); - super.visitInsn(Opcodes.AASTORE); - } - super.visitMethodInsn(Opcodes.INVOKESTATIC, - "org/apidesign/html/boot/impl/FnUtils", "define", - "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;" - ); - super.visitInsn(Opcodes.DUP); - super.visitFieldInsn( - Opcodes.PUTSTATIC, FindInClass.this.name, - "$$fn$$" + name + "_" + found, - "Lorg/apidesign/html/boot/spi/Fn;" - ); - // end of Fn init - - super.visitLabel(ifNotNull); - - final int offset; - if ((access & Opcodes.ACC_STATIC) == 0) { - offset = 1; - super.visitIntInsn(Opcodes.ALOAD, 0); - } else { - offset = 0; - super.visitInsn(Opcodes.ACONST_NULL); - } - - super.visitIntInsn(Opcodes.SIPUSH, args.size()); - super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); - - class SV extends SignatureVisitor { - private boolean nowReturn; - private Type returnType; - private int index; - private int loadIndex = offset; - - public SV() { - super(Opcodes.ASM4); - } - - @Override - public void visitBaseType(char descriptor) { - final Type t = Type.getType("" + descriptor); - if (nowReturn) { - returnType = t; - return; - } - FindInMethod.super.visitInsn(Opcodes.DUP); - FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++); - FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++); - String factory; - switch (descriptor) { - case 'I': factory = "java/lang/Integer"; break; - case 'J': factory = "java/lang/Long"; loadIndex++; break; - case 'S': factory = "java/lang/Short"; break; - case 'F': factory = "java/lang/Float"; break; - case 'D': factory = "java/lang/Double"; loadIndex++; break; - case 'Z': factory = "java/lang/Boolean"; break; - case 'C': factory = "java/lang/Character"; break; - case 'B': factory = "java/lang/Byte"; break; - default: throw new IllegalStateException(t.toString()); - } - FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC, - factory, "valueOf", "(" + descriptor + ")L" + factory + ";" - ); - FindInMethod.super.visitInsn(Opcodes.AASTORE); - } - - @Override - public SignatureVisitor visitArrayType() { - if (nowReturn) { - throw new IllegalStateException("Not supported yet"); - } - loadObject(); - return new SignatureWriter(); - } - - @Override - public void visitClassType(String name) { - if (nowReturn) { - returnType = Type.getObjectType(name); - return; - } - loadObject(); - } - - @Override - public SignatureVisitor visitReturnType() { - nowReturn = true; - return this; - } - - private void loadObject() { - FindInMethod.super.visitInsn(Opcodes.DUP); - FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++); - FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++); - FindInMethod.super.visitInsn(Opcodes.AASTORE); - } - - } - SV sv = new SV(); - SignatureReader sr = new SignatureReader(desc); - sr.accept(sv); - - if (needsVM) { - FindInMethod.super.visitInsn(Opcodes.DUP); - FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index); - int lastSlash = FindInClass.this.name.lastIndexOf('/'); - String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$"; - FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";"); - FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";"); - FindInMethod.super.visitInsn(Opcodes.AASTORE); - } - - super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;" - ); - switch (sv.returnType.getSort()) { - case Type.VOID: - super.visitInsn(Opcodes.RETURN); - break; - case Type.ARRAY: - case Type.OBJECT: - super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName()); - super.visitInsn(Opcodes.ARETURN); - break; - case Type.BOOLEAN: - super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean"); - super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "java/lang/Boolean", "booleanValue", "()Z" - ); - super.visitInsn(Opcodes.IRETURN); - break; - default: - super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number"); - super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor() - ); - super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN)); - } - return true; - } - - @Override - public void visitEnd() { - super.visitEnd(); - if (body != null) { - if (generateBody()) { - // native method - super.visitMaxs(1, 0); - } - FindInClass.this.visitField( - Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, - "$$fn$$" + name + "_" + found, - "Lorg/apidesign/html/boot/spi/Fn;", - null, null - ); - } - } - - - - - - private final class FindInAnno extends AnnotationVisitor { - private List args = new ArrayList(); - private String body; - private boolean javacall = false; - - public FindInAnno() { - super(Opcodes.ASM4); - } - - @Override - public void visit(String name, Object value) { - if (name == null) { - args.add((String) value); - return; - } - if (name.equals("javacall")) { // NOI18N - javacall = (Boolean)value; - return; - } - assert name.equals("body"); - body = (String) value; - } - - @Override - public AnnotationVisitor visitArray(String name) { - return this; - } - - @Override - public void visitEnd() { - if (body != null) { - if (javacall) { - body = FnUtils.callback(body); - args.add("vm"); - } - generateJSBody(args, body); - } - } - } - } - - private final class LoadResource extends AnnotationVisitor { - public LoadResource() { - super(Opcodes.ASM4); - } - - @Override - public void visit(String attrName, Object value) { - String relPath = (String) value; - if (relPath.startsWith("/")) { - FnUtils.loadScript(JsClassLoader.this, relPath); - } else { - int last = name.lastIndexOf('/'); - String fullPath = name.substring(0, last + 1) + relPath; - FnUtils.loadScript(JsClassLoader.this, fullPath); - } - } - } - } - - private class ClassWriterEx extends ClassWriter { - - public ClassWriterEx(ClassReader classReader, int flags) { - super(classReader, flags); - } - - @Override - protected String getCommonSuperClass(final String type1, final String type2) { - Class c, d; - ClassLoader classLoader = JsClassLoader.this; - try { - c = Class.forName(type1.replace('/', '.'), false, classLoader); - d = Class.forName(type2.replace('/', '.'), false, classLoader); - } catch (Exception e) { - throw new RuntimeException(e.toString()); - } - if (c.isAssignableFrom(d)) { - return type1; - } - if (d.isAssignableFrom(c)) { - return type2; - } - if (c.isInterface() || d.isInterface()) { - return "java/lang/Object"; - } else { - do { - c = c.getSuperclass(); - } while (!c.isAssignableFrom(d)); - return c.getName().replace('.', '/'); - } - } - } } diff -r 4a93f2679691 -r 86aabecda7a3 boot/src/main/java/org/apidesign/html/boot/impl/Test.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/Test.java Wed Nov 06 15:15:54 2013 +0100 @@ -0,0 +1,35 @@ +/** + * HTML via Java(tm) Language Bindings + * Copyright (C) 2013 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. apidesign.org + * designates this particular file as subject to the + * "Classpath" exception as provided by apidesign.org + * in the License file that accompanied this code. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException + */ +package org.apidesign.html.boot.impl; + +import java.util.concurrent.Callable; +import net.java.html.js.JavaScriptBody; + +/** + * + * @author Jaroslav Tulach + */ +public final class Test implements Callable { + @Override @JavaScriptBody(args = {}, body = "return true;") + public Boolean call() { + return false; + } +} diff -r 4a93f2679691 -r 86aabecda7a3 boot/src/main/java/org/apidesign/html/boot/spi/Fn.java --- a/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java Tue Nov 05 23:06:32 2013 +0100 +++ b/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java Wed Nov 06 15:15:54 2013 +0100 @@ -62,6 +62,31 @@ return FnContext.currentPresenter() == presenter; } + /** Helper method to check if the provided instance is valid function. + * Checks if the parameter is non-null and if so, does {@link #isValid()} + * check. + * + * @param fnOrNull function or null + * @return true if the parameter is non-null and valid + * @since 0.7 + */ + public static boolean isValid(Fn fnOrNull) { + return fnOrNull != null && fnOrNull.isValid(); + } + + /** Helper method to find current presenter and ask it to define new + * function by calling {@link Presenter#defineFn(java.lang.String, java.lang.String...)}. + * + * @param caller the class who wishes to define the function + * @param code the body of the function (can reference this and names variables) + * @param names names of individual parameters + * @return the function object that can be {@link Fn#invoke(java.lang.Object, java.lang.Object...) invoked} + * @since 0.7 + */ + public static Fn define(Class caller, String code, String... names) { + return FnContext.currentPresenter().defineFn(code, names); + } + /** The currently active presenter. * * @return the currently active presenter or null diff -r 4a93f2679691 -r 86aabecda7a3 boot/src/test/java/org/apidesign/html/boot/impl/FnTest.java --- a/boot/src/test/java/org/apidesign/html/boot/impl/FnTest.java Tue Nov 05 23:06:32 2013 +0100 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/FnTest.java Wed Nov 06 15:15:54 2013 +0100 @@ -21,6 +21,7 @@ package org.apidesign.html.boot.impl; +import java.io.Closeable; import java.io.Reader; import java.net.URL; import java.net.URLClassLoader; @@ -115,7 +116,9 @@ ClassLoader loader = FnUtils.newLoader(impl, impl, parent); presenter = impl; + Closeable close = FnContext.activate(impl); methodClass = loader.loadClass(JsMethods.class.getName()); + close.close(); } @BeforeMethod public void initPresenter() { diff -r 4a93f2679691 -r 86aabecda7a3 boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderTest.java --- a/boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderTest.java Tue Nov 05 23:06:32 2013 +0100 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderTest.java Wed Nov 06 15:15:54 2013 +0100 @@ -20,6 +20,7 @@ */ package org.apidesign.html.boot.impl; +import java.io.Closeable; import java.io.Reader; import org.apidesign.html.boot.spi.Fn; import java.net.URL; @@ -115,7 +116,9 @@ }; MyCL l = new MyCL(parent); + Closeable close = FnContext.activate(l); methodClass = l.loadClass(JsMethods.class.getName()); + close.close(); loader = l; }