Introducing Agent-Class to allow java.lang.instrument-like transforms of the @JavaScriptBody annotation
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Wed, 06 Nov 2013 15:15:54 +0100
changeset 32386aabecda7a3
parent 322 4a93f2679691
child 324 c6504f41922a
Introducing Agent-Class to allow java.lang.instrument-like transforms of the @JavaScriptBody annotation
boot/pom.xml
boot/src/main/java/org/apidesign/html/boot/impl/FnContext.java
boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java
boot/src/main/java/org/apidesign/html/boot/impl/JsAgent.java
boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java
boot/src/main/java/org/apidesign/html/boot/impl/Test.java
boot/src/main/java/org/apidesign/html/boot/spi/Fn.java
boot/src/test/java/org/apidesign/html/boot/impl/FnTest.java
boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderTest.java
     1.1 --- a/boot/pom.xml	Tue Nov 05 23:06:32 2013 +0100
     1.2 +++ b/boot/pom.xml	Wed Nov 06 15:15:54 2013 +0100
     1.3 @@ -21,6 +21,11 @@
     1.4            <plugin>
     1.5                <groupId>org.apache.felix</groupId>
     1.6                <artifactId>maven-bundle-plugin</artifactId>
     1.7 +              <configuration>
     1.8 +                  <instructions>
     1.9 +                      <Agent-Class>org.apidesign.html.boot.impl.JsAgent</Agent-Class>
    1.10 +                  </instructions>
    1.11 +              </configuration>
    1.12            </plugin>
    1.13           <plugin>
    1.14              <groupId>org.apache.maven.plugins</groupId>
     2.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/FnContext.java	Tue Nov 05 23:06:32 2013 +0100
     2.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/FnContext.java	Wed Nov 06 15:15:54 2013 +0100
     2.3 @@ -32,16 +32,16 @@
     2.4  public final class FnContext implements Closeable {
     2.5      private static final Logger LOG = Logger.getLogger(FnContext.class.getName());
     2.6  
     2.7 -    private Fn.Presenter prev;
     2.8 +    private Object prev;
     2.9      private FnContext(Fn.Presenter p) {
    2.10          this.prev = p;
    2.11      }
    2.12  
    2.13      @Override
    2.14      public void close() throws IOException {
    2.15 -        if (prev != null) {
    2.16 -            currentPresenter(prev);
    2.17 -            prev = null;
    2.18 +        if (prev != this) {
    2.19 +            currentPresenter((Fn.Presenter)prev);
    2.20 +            prev = this;
    2.21          }
    2.22      }
    2.23  /*
     3.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java	Tue Nov 05 23:06:32 2013 +0100
     3.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java	Wed Nov 06 15:15:54 2013 +0100
     3.3 @@ -20,6 +20,7 @@
     3.4   */
     3.5  package org.apidesign.html.boot.impl;
     3.6  
     3.7 +import java.io.Closeable;
     3.8  import java.io.InputStream;
     3.9  import java.io.InputStreamReader;
    3.10  import java.io.Reader;
    3.11 @@ -28,23 +29,41 @@
    3.12  import java.util.Collections;
    3.13  import java.util.Enumeration;
    3.14  import java.util.List;
    3.15 +import java.util.concurrent.Callable;
    3.16  import org.apidesign.html.boot.spi.Fn;
    3.17 +import org.objectweb.asm.AnnotationVisitor;
    3.18 +import org.objectweb.asm.ClassReader;
    3.19 +import org.objectweb.asm.ClassVisitor;
    3.20 +import org.objectweb.asm.ClassWriter;
    3.21 +import org.objectweb.asm.Label;
    3.22 +import org.objectweb.asm.MethodVisitor;
    3.23 +import org.objectweb.asm.Opcodes;
    3.24 +import org.objectweb.asm.Type;
    3.25 +import org.objectweb.asm.signature.SignatureReader;
    3.26 +import org.objectweb.asm.signature.SignatureVisitor;
    3.27 +import org.objectweb.asm.signature.SignatureWriter;
    3.28  
    3.29  /**
    3.30   *
    3.31   * @author Jaroslav Tulach <jtulach@netbeans.org>
    3.32   */
    3.33 -public final class FnUtils {
    3.34 +public final class FnUtils implements Fn.Presenter {
    3.35      
    3.36      private FnUtils() {
    3.37      }
    3.38      
    3.39 -    public static Fn define(Class<?> caller, String code, String... names) {
    3.40 -        return FnContext.currentPresenter().defineFn(code, names);
    3.41 -    }
    3.42 -    
    3.43      public static boolean isJavaScriptCapable(ClassLoader l) {
    3.44 -        return l instanceof JsClassLoader;
    3.45 +        if (l instanceof JsClassLoader) {
    3.46 +            return true;
    3.47 +        }
    3.48 +        Class<?> clazz;
    3.49 +        try (Closeable c = Fn.activate(new FnUtils())) {
    3.50 +            clazz = Class.forName(Test.class.getName(), true, l);
    3.51 +            final Object is = ((Callable<?>)clazz.newInstance()).call();
    3.52 +            return Boolean.TRUE.equals(is);
    3.53 +        } catch (Exception ex) {
    3.54 +            return false;
    3.55 +        }
    3.56      }
    3.57      
    3.58      public static boolean isValid(Fn fn) {
    3.59 @@ -100,7 +119,7 @@
    3.60          }.parse(body);
    3.61      }
    3.62  
    3.63 -    static void loadScript(JsClassLoader jcl, String resource) {
    3.64 +    static void loadScript(ClassLoader jcl, String resource) {
    3.65          final InputStream script = jcl.getResourceAsStream(resource);
    3.66          if (script == null) {
    3.67              throw new NullPointerException("Can't find " + resource);
    3.68 @@ -109,7 +128,7 @@
    3.69              Reader isr = null;
    3.70              try {
    3.71                  isr = new InputStreamReader(script, "UTF-8");
    3.72 -                jcl.loadScript(isr);
    3.73 +                FnContext.currentPresenter().loadScript(isr);
    3.74              } finally {
    3.75                  if (isr != null) {
    3.76                      isr.close();
    3.77 @@ -119,4 +138,429 @@
    3.78              throw new IllegalStateException("Can't execute " + resource, ex);
    3.79          } 
    3.80      }
    3.81 +
    3.82 +    @Override
    3.83 +    public Fn defineFn(String code, String... names) {
    3.84 +        return new TrueFn();
    3.85 +    }
    3.86 +
    3.87 +    @Override
    3.88 +    public void displayPage(URL page, Runnable onPageLoad) {
    3.89 +    }
    3.90 +
    3.91 +    @Override
    3.92 +    public void loadScript(Reader code) throws Exception {
    3.93 +    }
    3.94 +    
    3.95 +    private static final class FindInClass extends ClassVisitor {
    3.96 +        private String name;
    3.97 +        private int found;
    3.98 +        private ClassLoader loader;
    3.99 +
   3.100 +        public FindInClass(ClassLoader l, ClassVisitor cv) {
   3.101 +            super(Opcodes.ASM4, cv);
   3.102 +            this.loader = l;
   3.103 +        }
   3.104 +
   3.105 +        @Override
   3.106 +        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
   3.107 +            this.name = name;
   3.108 +            super.visit(version, access, name, signature, superName, interfaces);
   3.109 +        }
   3.110 +
   3.111 +        @Override
   3.112 +        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
   3.113 +            if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) {
   3.114 +                return new LoadResource();
   3.115 +            }
   3.116 +            return super.visitAnnotation(desc, visible);
   3.117 +        }
   3.118 +
   3.119 +        @Override
   3.120 +        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
   3.121 +            return new FindInMethod(access, name, desc,
   3.122 +                    super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions)
   3.123 +            );
   3.124 +        }
   3.125 +
   3.126 +        private final class FindInMethod extends MethodVisitor {
   3.127 +
   3.128 +            private final String name;
   3.129 +            private final String desc;
   3.130 +            private final int access;
   3.131 +            private List<String> args;
   3.132 +            private String body;
   3.133 +            private boolean bodyGenerated;
   3.134 +
   3.135 +            public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
   3.136 +                super(Opcodes.ASM4, mv);
   3.137 +                this.access = access;
   3.138 +                this.name = name;
   3.139 +                this.desc = desc;
   3.140 +            }
   3.141 +
   3.142 +            @Override
   3.143 +            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
   3.144 +                if ("Lnet/java/html/js/JavaScriptBody;".equals(desc) // NOI18N
   3.145 +                        || "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;".equals(desc) // NOI18N
   3.146 +                        ) {
   3.147 +                    found++;
   3.148 +                    return new FindInAnno();
   3.149 +                }
   3.150 +                return super.visitAnnotation(desc, visible);
   3.151 +            }
   3.152 +
   3.153 +            private void generateJSBody(List<String> args, String body) {
   3.154 +                this.args = args;
   3.155 +                this.body = body;
   3.156 +            }
   3.157 +
   3.158 +            @Override
   3.159 +            public void visitCode() {
   3.160 +                if (body == null) {
   3.161 +                    return;
   3.162 +                }
   3.163 +                generateBody();
   3.164 +            }
   3.165 +
   3.166 +            private boolean generateBody() {
   3.167 +                if (bodyGenerated) {
   3.168 +                    return false;
   3.169 +                }
   3.170 +                bodyGenerated = true;
   3.171 +
   3.172 +                super.visitFieldInsn(
   3.173 +                        Opcodes.GETSTATIC, FindInClass.this.name,
   3.174 +                        "$$fn$$" + name + "_" + found,
   3.175 +                        "Lorg/apidesign/html/boot/spi/Fn;"
   3.176 +                );
   3.177 +                super.visitInsn(Opcodes.DUP);
   3.178 +                super.visitMethodInsn(
   3.179 +                        Opcodes.INVOKESTATIC,
   3.180 +                        "org/apidesign/html/boot/spi/Fn", "isValid",
   3.181 +                        "(Lorg/apidesign/html/boot/spi/Fn;)Z"
   3.182 +                );
   3.183 +                Label ifNotNull = new Label();
   3.184 +                super.visitJumpInsn(Opcodes.IFNE, ifNotNull);
   3.185 +
   3.186 +                // init Fn
   3.187 +                super.visitInsn(Opcodes.POP);
   3.188 +                super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
   3.189 +                super.visitLdcInsn(body);
   3.190 +                super.visitIntInsn(Opcodes.SIPUSH, args.size());
   3.191 +                super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
   3.192 +                boolean needsVM = false;
   3.193 +                for (int i = 0; i < args.size(); i++) {
   3.194 +                    assert !needsVM;
   3.195 +                    String argName = args.get(i);
   3.196 +                    needsVM = "vm".equals(argName);
   3.197 +                    super.visitInsn(Opcodes.DUP);
   3.198 +                    super.visitIntInsn(Opcodes.BIPUSH, i);
   3.199 +                    super.visitLdcInsn(argName);
   3.200 +                    super.visitInsn(Opcodes.AASTORE);
   3.201 +                }
   3.202 +                super.visitMethodInsn(Opcodes.INVOKESTATIC,
   3.203 +                        "org/apidesign/html/boot/spi/Fn", "define",
   3.204 +                        "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
   3.205 +                );
   3.206 +                super.visitInsn(Opcodes.DUP);
   3.207 +                super.visitFieldInsn(
   3.208 +                        Opcodes.PUTSTATIC, FindInClass.this.name,
   3.209 +                        "$$fn$$" + name + "_" + found,
   3.210 +                        "Lorg/apidesign/html/boot/spi/Fn;"
   3.211 +                );
   3.212 +                // end of Fn init
   3.213 +
   3.214 +                super.visitLabel(ifNotNull);
   3.215 +
   3.216 +                final int offset;
   3.217 +                if ((access & Opcodes.ACC_STATIC) == 0) {
   3.218 +                    offset = 1;
   3.219 +                    super.visitIntInsn(Opcodes.ALOAD, 0);
   3.220 +                } else {
   3.221 +                    offset = 0;
   3.222 +                    super.visitInsn(Opcodes.ACONST_NULL);
   3.223 +                }
   3.224 +
   3.225 +                super.visitIntInsn(Opcodes.SIPUSH, args.size());
   3.226 +                super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
   3.227 +
   3.228 +                class SV extends SignatureVisitor {
   3.229 +
   3.230 +                    private boolean nowReturn;
   3.231 +                    private Type returnType;
   3.232 +                    private int index;
   3.233 +                    private int loadIndex = offset;
   3.234 +
   3.235 +                    public SV() {
   3.236 +                        super(Opcodes.ASM4);
   3.237 +                    }
   3.238 +
   3.239 +                    @Override
   3.240 +                    public void visitBaseType(char descriptor) {
   3.241 +                        final Type t = Type.getType("" + descriptor);
   3.242 +                        if (nowReturn) {
   3.243 +                            returnType = t;
   3.244 +                            return;
   3.245 +                        }
   3.246 +                        FindInMethod.super.visitInsn(Opcodes.DUP);
   3.247 +                        FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
   3.248 +                        FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
   3.249 +                        String factory;
   3.250 +                        switch (descriptor) {
   3.251 +                            case 'I':
   3.252 +                                factory = "java/lang/Integer";
   3.253 +                                break;
   3.254 +                            case 'J':
   3.255 +                                factory = "java/lang/Long";
   3.256 +                                loadIndex++;
   3.257 +                                break;
   3.258 +                            case 'S':
   3.259 +                                factory = "java/lang/Short";
   3.260 +                                break;
   3.261 +                            case 'F':
   3.262 +                                factory = "java/lang/Float";
   3.263 +                                break;
   3.264 +                            case 'D':
   3.265 +                                factory = "java/lang/Double";
   3.266 +                                loadIndex++;
   3.267 +                                break;
   3.268 +                            case 'Z':
   3.269 +                                factory = "java/lang/Boolean";
   3.270 +                                break;
   3.271 +                            case 'C':
   3.272 +                                factory = "java/lang/Character";
   3.273 +                                break;
   3.274 +                            case 'B':
   3.275 +                                factory = "java/lang/Byte";
   3.276 +                                break;
   3.277 +                            default:
   3.278 +                                throw new IllegalStateException(t.toString());
   3.279 +                        }
   3.280 +                        FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
   3.281 +                                factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
   3.282 +                        );
   3.283 +                        FindInMethod.super.visitInsn(Opcodes.AASTORE);
   3.284 +                    }
   3.285 +
   3.286 +                    @Override
   3.287 +                    public SignatureVisitor visitArrayType() {
   3.288 +                        if (nowReturn) {
   3.289 +                            throw new IllegalStateException("Not supported yet");
   3.290 +                        }
   3.291 +                        loadObject();
   3.292 +                        return new SignatureWriter();
   3.293 +                    }
   3.294 +
   3.295 +                    @Override
   3.296 +                    public void visitClassType(String name) {
   3.297 +                        if (nowReturn) {
   3.298 +                            returnType = Type.getObjectType(name);
   3.299 +                            return;
   3.300 +                        }
   3.301 +                        loadObject();
   3.302 +                    }
   3.303 +
   3.304 +                    @Override
   3.305 +                    public SignatureVisitor visitReturnType() {
   3.306 +                        nowReturn = true;
   3.307 +                        return this;
   3.308 +                    }
   3.309 +
   3.310 +                    private void loadObject() {
   3.311 +                        FindInMethod.super.visitInsn(Opcodes.DUP);
   3.312 +                        FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
   3.313 +                        FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++);
   3.314 +                        FindInMethod.super.visitInsn(Opcodes.AASTORE);
   3.315 +                    }
   3.316 +
   3.317 +                }
   3.318 +                SV sv = new SV();
   3.319 +                SignatureReader sr = new SignatureReader(desc);
   3.320 +                sr.accept(sv);
   3.321 +
   3.322 +                if (needsVM) {
   3.323 +                    FindInMethod.super.visitInsn(Opcodes.DUP);
   3.324 +                    FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index);
   3.325 +                    int lastSlash = FindInClass.this.name.lastIndexOf('/');
   3.326 +                    String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
   3.327 +                    FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
   3.328 +                    FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";");
   3.329 +                    FindInMethod.super.visitInsn(Opcodes.AASTORE);
   3.330 +                }
   3.331 +
   3.332 +                super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   3.333 +                        "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
   3.334 +                );
   3.335 +                switch (sv.returnType.getSort()) {
   3.336 +                    case Type.VOID:
   3.337 +                        super.visitInsn(Opcodes.RETURN);
   3.338 +                        break;
   3.339 +                    case Type.ARRAY:
   3.340 +                    case Type.OBJECT:
   3.341 +                        super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
   3.342 +                        super.visitInsn(Opcodes.ARETURN);
   3.343 +                        break;
   3.344 +                    case Type.BOOLEAN:
   3.345 +                        super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
   3.346 +                        super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   3.347 +                                "java/lang/Boolean", "booleanValue", "()Z"
   3.348 +                        );
   3.349 +                        super.visitInsn(Opcodes.IRETURN);
   3.350 +                        break;
   3.351 +                    default:
   3.352 +                        super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
   3.353 +                        super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   3.354 +                                "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
   3.355 +                        );
   3.356 +                        super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
   3.357 +                }
   3.358 +                return true;
   3.359 +            }
   3.360 +
   3.361 +            @Override
   3.362 +            public void visitEnd() {
   3.363 +                super.visitEnd();
   3.364 +                if (body != null) {
   3.365 +                    if (generateBody()) {
   3.366 +                        // native method
   3.367 +                        super.visitMaxs(1, 0);
   3.368 +                    }
   3.369 +                    FindInClass.this.visitField(
   3.370 +                            Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
   3.371 +                            "$$fn$$" + name + "_" + found,
   3.372 +                            "Lorg/apidesign/html/boot/spi/Fn;",
   3.373 +                            null, null
   3.374 +                    );
   3.375 +                }
   3.376 +            }
   3.377 +
   3.378 +            private final class FindInAnno extends AnnotationVisitor {
   3.379 +
   3.380 +                private List<String> args = new ArrayList<String>();
   3.381 +                private String body;
   3.382 +                private boolean javacall = false;
   3.383 +
   3.384 +                public FindInAnno() {
   3.385 +                    super(Opcodes.ASM4);
   3.386 +                }
   3.387 +
   3.388 +                @Override
   3.389 +                public void visit(String name, Object value) {
   3.390 +                    if (name == null) {
   3.391 +                        args.add((String) value);
   3.392 +                        return;
   3.393 +                    }
   3.394 +                    if (name.equals("javacall")) { // NOI18N
   3.395 +                        javacall = (Boolean) value;
   3.396 +                        return;
   3.397 +                    }
   3.398 +                    assert name.equals("body");
   3.399 +                    body = (String) value;
   3.400 +                }
   3.401 +
   3.402 +                @Override
   3.403 +                public AnnotationVisitor visitArray(String name) {
   3.404 +                    return this;
   3.405 +                }
   3.406 +
   3.407 +                @Override
   3.408 +                public void visitEnd() {
   3.409 +                    if (body != null) {
   3.410 +                        if (javacall) {
   3.411 +                            body = callback(body);
   3.412 +                            args.add("vm");
   3.413 +                        }
   3.414 +                        generateJSBody(args, body);
   3.415 +                    }
   3.416 +                }
   3.417 +            }
   3.418 +        }
   3.419 +
   3.420 +        private final class LoadResource extends AnnotationVisitor {
   3.421 +
   3.422 +            public LoadResource() {
   3.423 +                super(Opcodes.ASM4);
   3.424 +            }
   3.425 +
   3.426 +            @Override
   3.427 +            public void visit(String attrName, Object value) {
   3.428 +                String relPath = (String) value;
   3.429 +                if (relPath.startsWith("/")) {
   3.430 +                    loadScript(loader, relPath);
   3.431 +                } else {
   3.432 +                    int last = name.lastIndexOf('/');
   3.433 +                    String fullPath = name.substring(0, last + 1) + relPath;
   3.434 +                    loadScript(loader, fullPath);
   3.435 +                }
   3.436 +            }
   3.437 +        }
   3.438 +    }
   3.439 +
   3.440 +    private static class ClassWriterEx extends ClassWriter {
   3.441 +
   3.442 +        private ClassLoader loader;
   3.443 +
   3.444 +        public ClassWriterEx(ClassLoader l, ClassReader classReader, int flags) {
   3.445 +            super(classReader, flags);
   3.446 +            this.loader = l;
   3.447 +        }
   3.448 +
   3.449 +        @Override
   3.450 +        protected String getCommonSuperClass(final String type1, final String type2) {
   3.451 +            Class<?> c, d;
   3.452 +            try {
   3.453 +                c = Class.forName(type1.replace('/', '.'), false, loader);
   3.454 +                d = Class.forName(type2.replace('/', '.'), false, loader);
   3.455 +            } catch (Exception e) {
   3.456 +                throw new RuntimeException(e.toString());
   3.457 +            }
   3.458 +            if (c.isAssignableFrom(d)) {
   3.459 +                return type1;
   3.460 +            }
   3.461 +            if (d.isAssignableFrom(c)) {
   3.462 +                return type2;
   3.463 +            }
   3.464 +            if (c.isInterface() || d.isInterface()) {
   3.465 +                return "java/lang/Object";
   3.466 +            } else {
   3.467 +                do {
   3.468 +                    c = c.getSuperclass();
   3.469 +                } while (!c.isAssignableFrom(d));
   3.470 +                return c.getName().replace('.', '/');
   3.471 +            }
   3.472 +        }
   3.473 +    }
   3.474 +
   3.475 +    static byte[] transform(ClassLoader loader, byte[] arr) {
   3.476 +        ClassReader cr = new ClassReader(arr) {
   3.477 +            // to allow us to compile with -profile compact1 on 
   3.478 +            // JDK8 while processing the class as JDK7, the highest
   3.479 +            // class format asm 4.1 understands to
   3.480 +            @Override
   3.481 +            public short readShort(int index) {
   3.482 +                short s = super.readShort(index);
   3.483 +                if (index == 6 && s > Opcodes.V1_7) {
   3.484 +                    return Opcodes.V1_7;
   3.485 +                }
   3.486 +                return s;
   3.487 +            }
   3.488 +        };
   3.489 +        FindInClass tst = new FindInClass(loader, null);
   3.490 +        cr.accept(tst, 0);
   3.491 +        if (tst.found > 0) {
   3.492 +            ClassWriter w = new ClassWriterEx(loader, cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
   3.493 +            FindInClass fic = new FindInClass(loader, w);
   3.494 +            cr.accept(fic, 0);
   3.495 +            arr = w.toByteArray();
   3.496 +        }
   3.497 +        return arr;
   3.498 +    }
   3.499 +
   3.500 +    private static final class TrueFn extends Fn {
   3.501 +        @Override
   3.502 +        public Object invoke(Object thiz, Object... args) throws Exception {
   3.503 +            return Boolean.TRUE;
   3.504 +        }
   3.505 +    } // end of TrueFn
   3.506  }
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JsAgent.java	Wed Nov 06 15:15:54 2013 +0100
     4.3 @@ -0,0 +1,45 @@
     4.4 +/**
     4.5 + * HTML via Java(tm) Language Bindings
     4.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4.7 + *
     4.8 + * This program is free software: you can redistribute it and/or modify
     4.9 + * it under the terms of the GNU General Public License as published by
    4.10 + * the Free Software Foundation, version 2 of the License.
    4.11 + *
    4.12 + * This program is distributed in the hope that it will be useful,
    4.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    4.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    4.15 + * GNU General Public License for more details. apidesign.org
    4.16 + * designates this particular file as subject to the
    4.17 + * "Classpath" exception as provided by apidesign.org
    4.18 + * in the License file that accompanied this code.
    4.19 + *
    4.20 + * You should have received a copy of the GNU General Public License
    4.21 + * along with this program. Look for COPYING file in the top folder.
    4.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    4.23 + */
    4.24 +package org.apidesign.html.boot.impl;
    4.25 +
    4.26 +import java.lang.instrument.ClassFileTransformer;
    4.27 +import java.lang.instrument.IllegalClassFormatException;
    4.28 +import java.lang.instrument.Instrumentation;
    4.29 +import java.security.ProtectionDomain;
    4.30 +
    4.31 +/**
    4.32 + *
    4.33 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    4.34 + */
    4.35 +public final class JsAgent implements ClassFileTransformer {
    4.36 +    public static void agentmain(String args, Instrumentation instr) {
    4.37 +        instr.addTransformer(new JsAgent());
    4.38 +    }
    4.39 +
    4.40 +    @Override
    4.41 +    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
    4.42 +        try {
    4.43 +            return FnUtils.transform(loader, classfileBuffer);
    4.44 +        } catch (Exception ex) {
    4.45 +            return classfileBuffer;
    4.46 +        }
    4.47 +    }
    4.48 +}
     5.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java	Tue Nov 05 23:06:32 2013 +0100
     5.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java	Wed Nov 06 15:15:54 2013 +0100
     5.3 @@ -25,20 +25,7 @@
     5.4  import java.io.InputStream;
     5.5  import java.io.Reader;
     5.6  import java.net.URL;
     5.7 -import java.util.ArrayList;
     5.8  import java.util.Enumeration;
     5.9 -import java.util.List;
    5.10 -import org.objectweb.asm.AnnotationVisitor;
    5.11 -import org.objectweb.asm.ClassReader;
    5.12 -import org.objectweb.asm.ClassVisitor;
    5.13 -import org.objectweb.asm.ClassWriter;
    5.14 -import org.objectweb.asm.Label;
    5.15 -import org.objectweb.asm.MethodVisitor;
    5.16 -import org.objectweb.asm.Opcodes;
    5.17 -import org.objectweb.asm.Type;
    5.18 -import org.objectweb.asm.signature.SignatureReader;
    5.19 -import org.objectweb.asm.signature.SignatureVisitor;
    5.20 -import org.objectweb.asm.signature.SignatureWriter;
    5.21  
    5.22  /** 
    5.23   *
    5.24 @@ -102,27 +89,7 @@
    5.25                  }
    5.26                  is.close();
    5.27                  is = null;
    5.28 -                ClassReader cr = new ClassReader(arr) {
    5.29 -                    // to allow us to compile with -profile compact1 on 
    5.30 -                    // JDK8 while processing the class as JDK7, the highest
    5.31 -                    // class format asm 4.1 understands to
    5.32 -                    @Override
    5.33 -                    public short readShort(int index) {
    5.34 -                        short s = super.readShort(index);
    5.35 -                        if (index == 6 && s > Opcodes.V1_7) {
    5.36 -                            return Opcodes.V1_7;
    5.37 -                        }
    5.38 -                        return s;
    5.39 -                    }
    5.40 -                };
    5.41 -                FindInClass tst = new FindInClass(null);
    5.42 -                cr.accept(tst, 0);
    5.43 -                if (tst.found > 0) {
    5.44 -                    ClassWriter w = new ClassWriterEx(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
    5.45 -                    FindInClass fic = new FindInClass(w);
    5.46 -                    cr.accept(fic, 0);
    5.47 -                    arr = w.toByteArray();
    5.48 -                }
    5.49 +                arr = FnUtils.transform(JsClassLoader.this, arr);
    5.50                  if (arr != null) {
    5.51                      return defineClass(name, arr, 0, arr.length);
    5.52                  }
    5.53 @@ -141,363 +108,4 @@
    5.54      
    5.55      protected abstract Fn defineFn(String code, String... names);
    5.56      protected abstract void loadScript(Reader code) throws Exception;
    5.57 -    
    5.58 -    private final class FindInClass extends ClassVisitor {
    5.59 -        private String name;
    5.60 -        private int found;
    5.61 -        
    5.62 -        public FindInClass(ClassVisitor cv) {
    5.63 -            super(Opcodes.ASM4, cv);
    5.64 -        }
    5.65 -
    5.66 -        @Override
    5.67 -        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
    5.68 -            this.name = name;
    5.69 -            super.visit(version, access, name, signature, superName, interfaces);
    5.70 -        }
    5.71 -
    5.72 -        @Override
    5.73 -        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    5.74 -            if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) {
    5.75 -                return new LoadResource();
    5.76 -            }
    5.77 -            return super.visitAnnotation(desc, visible);
    5.78 -        }
    5.79 -        
    5.80 -
    5.81 -        @Override
    5.82 -        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
    5.83 -            return new FindInMethod(access, name, desc,
    5.84 -                super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions)
    5.85 -            );
    5.86 -        }
    5.87 -        
    5.88 -        private final class FindInMethod extends MethodVisitor {
    5.89 -            private final String name;
    5.90 -            private final String desc;
    5.91 -            private final int access;
    5.92 -            private List<String> args;
    5.93 -            private String body;
    5.94 -            private boolean bodyGenerated;
    5.95 -            
    5.96 -            public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
    5.97 -                super(Opcodes.ASM4, mv);
    5.98 -                this.access = access;
    5.99 -                this.name = name;
   5.100 -                this.desc = desc;
   5.101 -            }
   5.102 -
   5.103 -            @Override
   5.104 -            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
   5.105 -                if (
   5.106 -                    "Lnet/java/html/js/JavaScriptBody;".equals(desc) // NOI18N
   5.107 -                    || "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;".equals(desc) // NOI18N
   5.108 -                ) {
   5.109 -                    found++;
   5.110 -                    return new FindInAnno();
   5.111 -                }
   5.112 -                return super.visitAnnotation(desc, visible);
   5.113 -            }
   5.114 -
   5.115 -            private void generateJSBody(List<String> args, String body) {
   5.116 -                this.args = args;
   5.117 -                this.body = body;
   5.118 -            }
   5.119 -            
   5.120 -            @Override
   5.121 -            public void visitCode() {
   5.122 -                if (body == null) {
   5.123 -                    return;
   5.124 -                } 
   5.125 -                generateBody();
   5.126 -            }
   5.127 -            
   5.128 -            private boolean generateBody() {
   5.129 -                if (bodyGenerated) {
   5.130 -                    return false;
   5.131 -                }
   5.132 -                bodyGenerated = true;
   5.133 -                
   5.134 -                super.visitFieldInsn(
   5.135 -                    Opcodes.GETSTATIC, FindInClass.this.name, 
   5.136 -                    "$$fn$$" + name + "_" + found, 
   5.137 -                    "Lorg/apidesign/html/boot/spi/Fn;"
   5.138 -                );
   5.139 -                super.visitInsn(Opcodes.DUP);
   5.140 -                super.visitMethodInsn(
   5.141 -                    Opcodes.INVOKESTATIC, 
   5.142 -                    "org/apidesign/html/boot/impl/FnUtils", "isValid", 
   5.143 -                    "(Lorg/apidesign/html/boot/spi/Fn;)Z"
   5.144 -                );
   5.145 -                Label ifNotNull = new Label();
   5.146 -                super.visitJumpInsn(Opcodes.IFNE, ifNotNull);
   5.147 -                
   5.148 -                // init Fn
   5.149 -                super.visitInsn(Opcodes.POP);
   5.150 -                super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
   5.151 -                super.visitLdcInsn(body);
   5.152 -                super.visitIntInsn(Opcodes.SIPUSH, args.size());
   5.153 -                super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
   5.154 -                boolean needsVM = false;
   5.155 -                for (int i = 0; i < args.size(); i++) {
   5.156 -                    assert !needsVM;
   5.157 -                    String argName = args.get(i);
   5.158 -                    needsVM = "vm".equals(argName);
   5.159 -                    super.visitInsn(Opcodes.DUP);
   5.160 -                    super.visitIntInsn(Opcodes.BIPUSH, i);
   5.161 -                    super.visitLdcInsn(argName);
   5.162 -                    super.visitInsn(Opcodes.AASTORE);
   5.163 -                }
   5.164 -                super.visitMethodInsn(Opcodes.INVOKESTATIC, 
   5.165 -                    "org/apidesign/html/boot/impl/FnUtils", "define", 
   5.166 -                    "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
   5.167 -                );
   5.168 -                super.visitInsn(Opcodes.DUP);
   5.169 -                super.visitFieldInsn(
   5.170 -                    Opcodes.PUTSTATIC, FindInClass.this.name, 
   5.171 -                    "$$fn$$" + name + "_" + found, 
   5.172 -                    "Lorg/apidesign/html/boot/spi/Fn;"
   5.173 -                );
   5.174 -                // end of Fn init
   5.175 -                
   5.176 -                super.visitLabel(ifNotNull);
   5.177 -                
   5.178 -                final int offset;
   5.179 -                if ((access & Opcodes.ACC_STATIC) == 0) {
   5.180 -                    offset = 1;
   5.181 -                    super.visitIntInsn(Opcodes.ALOAD, 0);
   5.182 -                } else {
   5.183 -                    offset = 0;
   5.184 -                    super.visitInsn(Opcodes.ACONST_NULL);
   5.185 -                }
   5.186 -                
   5.187 -                super.visitIntInsn(Opcodes.SIPUSH, args.size());
   5.188 -                super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
   5.189 -                
   5.190 -                class SV extends SignatureVisitor {
   5.191 -                    private boolean nowReturn;
   5.192 -                    private Type returnType;
   5.193 -                    private int index;
   5.194 -                    private int loadIndex = offset;
   5.195 -                    
   5.196 -                    public SV() {
   5.197 -                        super(Opcodes.ASM4);
   5.198 -                    }
   5.199 -                    
   5.200 -                    @Override
   5.201 -                    public void visitBaseType(char descriptor) {
   5.202 -                        final Type t = Type.getType("" + descriptor);
   5.203 -                        if (nowReturn) {
   5.204 -                            returnType = t;
   5.205 -                            return;
   5.206 -                        }
   5.207 -                        FindInMethod.super.visitInsn(Opcodes.DUP);
   5.208 -                        FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
   5.209 -                        FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
   5.210 -                        String factory;
   5.211 -                        switch (descriptor) {
   5.212 -                        case 'I': factory = "java/lang/Integer"; break;
   5.213 -                        case 'J': factory = "java/lang/Long"; loadIndex++; break;
   5.214 -                        case 'S': factory = "java/lang/Short"; break;
   5.215 -                        case 'F': factory = "java/lang/Float"; break;
   5.216 -                        case 'D': factory = "java/lang/Double"; loadIndex++; break;
   5.217 -                        case 'Z': factory = "java/lang/Boolean"; break;
   5.218 -                        case 'C': factory = "java/lang/Character"; break;
   5.219 -                        case 'B': factory = "java/lang/Byte"; break;
   5.220 -                        default: throw new IllegalStateException(t.toString());
   5.221 -                        }
   5.222 -                        FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
   5.223 -                            factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
   5.224 -                        );
   5.225 -                        FindInMethod.super.visitInsn(Opcodes.AASTORE);
   5.226 -                    }
   5.227 -
   5.228 -                    @Override
   5.229 -                    public SignatureVisitor visitArrayType() {
   5.230 -                        if (nowReturn) {
   5.231 -                            throw new IllegalStateException("Not supported yet");
   5.232 -                        }
   5.233 -                        loadObject();
   5.234 -                        return new SignatureWriter();
   5.235 -                    }
   5.236 -
   5.237 -                    @Override
   5.238 -                    public void visitClassType(String name) {
   5.239 -                        if (nowReturn) {
   5.240 -                            returnType = Type.getObjectType(name);
   5.241 -                            return;
   5.242 -                        }
   5.243 -                        loadObject();
   5.244 -                    }
   5.245 -
   5.246 -                    @Override
   5.247 -                    public SignatureVisitor visitReturnType() {
   5.248 -                        nowReturn = true;
   5.249 -                        return this;
   5.250 -                    }
   5.251 -
   5.252 -                    private void loadObject() {
   5.253 -                        FindInMethod.super.visitInsn(Opcodes.DUP);
   5.254 -                        FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
   5.255 -                        FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++);
   5.256 -                        FindInMethod.super.visitInsn(Opcodes.AASTORE);
   5.257 -                    }
   5.258 -                    
   5.259 -                }
   5.260 -                SV sv = new SV();
   5.261 -                SignatureReader sr = new SignatureReader(desc);
   5.262 -                sr.accept(sv);
   5.263 -                
   5.264 -                if (needsVM) {
   5.265 -                    FindInMethod.super.visitInsn(Opcodes.DUP);
   5.266 -                    FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index);
   5.267 -                    int lastSlash = FindInClass.this.name.lastIndexOf('/');
   5.268 -                    String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
   5.269 -                    FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
   5.270 -                    FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";");
   5.271 -                    FindInMethod.super.visitInsn(Opcodes.AASTORE);
   5.272 -                }
   5.273 -                
   5.274 -                super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   5.275 -                    "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
   5.276 -                );
   5.277 -                switch (sv.returnType.getSort()) {
   5.278 -                case Type.VOID: 
   5.279 -                    super.visitInsn(Opcodes.RETURN);
   5.280 -                    break;
   5.281 -                case Type.ARRAY:
   5.282 -                case Type.OBJECT:
   5.283 -                    super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
   5.284 -                    super.visitInsn(Opcodes.ARETURN);
   5.285 -                    break;
   5.286 -                case Type.BOOLEAN:
   5.287 -                    super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
   5.288 -                    super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   5.289 -                        "java/lang/Boolean", "booleanValue", "()Z"
   5.290 -                    );
   5.291 -                    super.visitInsn(Opcodes.IRETURN);
   5.292 -                    break;
   5.293 -                default:
   5.294 -                    super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
   5.295 -                    super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   5.296 -                        "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
   5.297 -                    );
   5.298 -                    super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
   5.299 -                }
   5.300 -                return true;
   5.301 -            }
   5.302 -
   5.303 -            @Override
   5.304 -            public void visitEnd() {
   5.305 -                super.visitEnd();
   5.306 -                if (body != null) {
   5.307 -                    if (generateBody()) {
   5.308 -                        // native method
   5.309 -                        super.visitMaxs(1, 0);
   5.310 -                    }
   5.311 -                    FindInClass.this.visitField(
   5.312 -                        Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, 
   5.313 -                        "$$fn$$" + name + "_" + found, 
   5.314 -                        "Lorg/apidesign/html/boot/spi/Fn;", 
   5.315 -                        null, null
   5.316 -                    );
   5.317 -                }
   5.318 -            }
   5.319 -            
   5.320 -            
   5.321 -            
   5.322 -            
   5.323 -        
   5.324 -            private final class FindInAnno extends AnnotationVisitor {
   5.325 -                private List<String> args = new ArrayList<String>();
   5.326 -                private String body;
   5.327 -                private boolean javacall = false;
   5.328 -
   5.329 -                public FindInAnno() {
   5.330 -                    super(Opcodes.ASM4);
   5.331 -                }
   5.332 -
   5.333 -                @Override
   5.334 -                public void visit(String name, Object value) {
   5.335 -                    if (name == null) {
   5.336 -                        args.add((String) value);
   5.337 -                        return;
   5.338 -                    }
   5.339 -                    if (name.equals("javacall")) { // NOI18N
   5.340 -                        javacall = (Boolean)value;
   5.341 -                        return;
   5.342 -                    }
   5.343 -                    assert name.equals("body");
   5.344 -                    body = (String) value;
   5.345 -                }
   5.346 -
   5.347 -                @Override
   5.348 -                public AnnotationVisitor visitArray(String name) {
   5.349 -                    return this;
   5.350 -                }
   5.351 -
   5.352 -                @Override
   5.353 -                public void visitEnd() {
   5.354 -                    if (body != null) {
   5.355 -                        if (javacall) {
   5.356 -                            body = FnUtils.callback(body);
   5.357 -                            args.add("vm");
   5.358 -                        }
   5.359 -                        generateJSBody(args, body);
   5.360 -                    }
   5.361 -                }
   5.362 -            }
   5.363 -        }
   5.364 -        
   5.365 -        private final class LoadResource extends AnnotationVisitor {
   5.366 -            public LoadResource() {
   5.367 -                super(Opcodes.ASM4);
   5.368 -            }
   5.369 -            
   5.370 -            @Override
   5.371 -            public void visit(String attrName, Object value)  {
   5.372 -                String relPath = (String) value;
   5.373 -                if (relPath.startsWith("/")) {
   5.374 -                    FnUtils.loadScript(JsClassLoader.this, relPath);
   5.375 -                } else {
   5.376 -                    int last = name.lastIndexOf('/');
   5.377 -                    String fullPath = name.substring(0, last + 1) + relPath;
   5.378 -                    FnUtils.loadScript(JsClassLoader.this, fullPath);
   5.379 -                }
   5.380 -            }
   5.381 -        }
   5.382 -    }
   5.383 -    
   5.384 -    private class ClassWriterEx extends ClassWriter {
   5.385 -
   5.386 -        public ClassWriterEx(ClassReader classReader, int flags) {
   5.387 -            super(classReader, flags);
   5.388 -        }
   5.389 -        
   5.390 -        @Override
   5.391 -        protected String getCommonSuperClass(final String type1, final String type2) {
   5.392 -            Class<?> c, d;
   5.393 -            ClassLoader classLoader = JsClassLoader.this;
   5.394 -            try {
   5.395 -                c = Class.forName(type1.replace('/', '.'), false, classLoader);
   5.396 -                d = Class.forName(type2.replace('/', '.'), false, classLoader);
   5.397 -            } catch (Exception e) {
   5.398 -                throw new RuntimeException(e.toString());
   5.399 -            }
   5.400 -            if (c.isAssignableFrom(d)) {
   5.401 -                return type1;
   5.402 -            }
   5.403 -            if (d.isAssignableFrom(c)) {
   5.404 -                return type2;
   5.405 -            }
   5.406 -            if (c.isInterface() || d.isInterface()) {
   5.407 -                return "java/lang/Object";
   5.408 -            } else {
   5.409 -                do {
   5.410 -                    c = c.getSuperclass();
   5.411 -                } while (!c.isAssignableFrom(d));
   5.412 -                return c.getName().replace('.', '/');
   5.413 -            }
   5.414 -        }        
   5.415 -    }
   5.416  }
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/Test.java	Wed Nov 06 15:15:54 2013 +0100
     6.3 @@ -0,0 +1,35 @@
     6.4 +/**
     6.5 + * HTML via Java(tm) Language Bindings
     6.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     6.7 + *
     6.8 + * This program is free software: you can redistribute it and/or modify
     6.9 + * it under the terms of the GNU General Public License as published by
    6.10 + * the Free Software Foundation, version 2 of the License.
    6.11 + *
    6.12 + * This program is distributed in the hope that it will be useful,
    6.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    6.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    6.15 + * GNU General Public License for more details. apidesign.org
    6.16 + * designates this particular file as subject to the
    6.17 + * "Classpath" exception as provided by apidesign.org
    6.18 + * in the License file that accompanied this code.
    6.19 + *
    6.20 + * You should have received a copy of the GNU General Public License
    6.21 + * along with this program. Look for COPYING file in the top folder.
    6.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    6.23 + */
    6.24 +package org.apidesign.html.boot.impl;
    6.25 +
    6.26 +import java.util.concurrent.Callable;
    6.27 +import net.java.html.js.JavaScriptBody;
    6.28 +
    6.29 +/**
    6.30 + *
    6.31 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    6.32 + */
    6.33 +public final class Test implements Callable<Boolean> {
    6.34 +    @Override @JavaScriptBody(args = {}, body = "return true;")
    6.35 +    public Boolean call() {
    6.36 +        return false;
    6.37 +    }
    6.38 +}
     7.1 --- a/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java	Tue Nov 05 23:06:32 2013 +0100
     7.2 +++ b/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java	Wed Nov 06 15:15:54 2013 +0100
     7.3 @@ -62,6 +62,31 @@
     7.4          return FnContext.currentPresenter() == presenter;
     7.5      }
     7.6      
     7.7 +    /** Helper method to check if the provided instance is valid function.
     7.8 +     * Checks if the parameter is non-null and if so, does {@link #isValid()}
     7.9 +     * check.
    7.10 +     * 
    7.11 +     * @param fnOrNull function or <code>null</code>
    7.12 +     * @return true if the parameter is non-null and valid
    7.13 +     * @since 0.7
    7.14 +     */
    7.15 +    public static boolean isValid(Fn fnOrNull) {
    7.16 +        return fnOrNull != null && fnOrNull.isValid();
    7.17 +    }
    7.18 +
    7.19 +    /** Helper method to find current presenter and ask it to define new
    7.20 +     * function by calling {@link Presenter#defineFn(java.lang.String, java.lang.String...)}.
    7.21 +     * 
    7.22 +     * @param caller the class who wishes to define the function
    7.23 +     * @param code the body of the function (can reference <code>this</code> and <code>names</code> variables)
    7.24 +     * @param names names of individual parameters
    7.25 +     * @return the function object that can be {@link Fn#invoke(java.lang.Object, java.lang.Object...) invoked}
    7.26 +     * @since 0.7
    7.27 +     */
    7.28 +    public static Fn define(Class<?> caller, String code, String... names) {
    7.29 +        return FnContext.currentPresenter().defineFn(code, names);
    7.30 +    }
    7.31 +    
    7.32      /** The currently active presenter.
    7.33       * 
    7.34       * @return the currently active presenter or <code>null</code>
     8.1 --- a/boot/src/test/java/org/apidesign/html/boot/impl/FnTest.java	Tue Nov 05 23:06:32 2013 +0100
     8.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/FnTest.java	Wed Nov 06 15:15:54 2013 +0100
     8.3 @@ -21,6 +21,7 @@
     8.4  
     8.5  package org.apidesign.html.boot.impl;
     8.6  
     8.7 +import java.io.Closeable;
     8.8  import java.io.Reader;
     8.9  import java.net.URL;
    8.10  import java.net.URLClassLoader;
    8.11 @@ -115,7 +116,9 @@
    8.12          ClassLoader loader = FnUtils.newLoader(impl, impl, parent);
    8.13          presenter = impl;
    8.14          
    8.15 +        Closeable close = FnContext.activate(impl);
    8.16          methodClass = loader.loadClass(JsMethods.class.getName());
    8.17 +        close.close();
    8.18      }
    8.19  
    8.20      @BeforeMethod public void initPresenter() {
     9.1 --- a/boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderTest.java	Tue Nov 05 23:06:32 2013 +0100
     9.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderTest.java	Wed Nov 06 15:15:54 2013 +0100
     9.3 @@ -20,6 +20,7 @@
     9.4   */
     9.5  package org.apidesign.html.boot.impl;
     9.6  
     9.7 +import java.io.Closeable;
     9.8  import java.io.Reader;
     9.9  import org.apidesign.html.boot.spi.Fn;
    9.10  import java.net.URL;
    9.11 @@ -115,7 +116,9 @@
    9.12          };
    9.13          
    9.14          MyCL l = new MyCL(parent);
    9.15 +        Closeable close = FnContext.activate(l);
    9.16          methodClass = l.loadClass(JsMethods.class.getName());
    9.17 +        close.close();
    9.18          loader = l;
    9.19      }
    9.20