You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by jt...@apache.org on 2017/09/03 12:48:52 UTC

[19/24] incubator-netbeans-html4j git commit: [INFRA-15006] Initial donation of HTML/Java NetBeans API. Equivalent to the content of html4j-donation-review.zip donated as part of ApacheNetBeansDonation1.zip with SHA256 being 7f2ca0f61953a190613c9a0fbcc1b

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java
----------------------------------------------------------------------
diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java
new file mode 100644
index 0000000..7abae31
--- /dev/null
+++ b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java
@@ -0,0 +1,717 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.boot.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import net.java.html.js.JavaScriptBody;
+import net.java.html.js.JavaScriptResource;
+import org.netbeans.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.FieldVisitor;
+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;
+
+/** Utilities related to bytecode transformations. Depend on asm.jar which
+ * needs to be added to be provided to classpath to make methods in this 
+ * class useful.
+ *
+ * @author Jaroslav Tulach
+ */
+public final class FnUtils {
+    
+    private FnUtils() {
+    }
+    
+    /** Seeks for {@link JavaScriptBody} and {@link JavaScriptResource} annotations
+     * in the bytecode and converts them into real code. Used by Maven plugin
+     * postprocessing classes.
+     * 
+     * @param bytecode the original bytecode with javascript specific annotations
+     * @param loader the loader to load resources (scripts and classes) when needed
+     * @return the transformed bytecode
+     * @since 0.7
+     */
+    public static byte[] transform(byte[] bytecode, ClassLoader loader) {
+        ClassReader cr = new ClassReader(bytecode) {
+            // 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);
+            bytecode = w.toByteArray();
+        }
+        return bytecode;
+    }
+    
+    public static ClassLoader newLoader(final FindResources f, final Fn.Presenter d, ClassLoader parent) {
+        return new JsClassLoaderImpl(parent, f, d);
+    }
+
+    static String callback(final String body) {
+        return new JsCallback() {
+            @Override
+            protected CharSequence callMethod(
+                String ident, String fqn, String method, String params
+            ) {
+                StringBuilder sb = new StringBuilder();
+                if (ident != null) {
+                    sb.append("vm.raw$");
+                } else {
+                    sb.append("vm.");
+                }
+                sb.append(mangle(fqn, method, params));
+                sb.append("(");
+                if (ident != null) {
+                    sb.append(ident);
+                }
+                return sb;
+            }
+
+        }.parse(body);
+    }
+
+    private static final class FindInClass extends ClassVisitor {
+        private String name;
+        private int found;
+        private String resource;
+
+        public FindInClass(ClassLoader l, 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) {
+            final AnnotationVisitor del = super.visitAnnotation(desc, visible);
+            if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) {
+                return new LoadResource(del);
+            }
+            return del;
+        }
+
+        @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)
+            );
+        }
+
+        @Override
+        public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+            if (name.startsWith("$$fn$$")) {
+                return null;
+            }
+            return superField(access, name, desc, signature, value);
+        }
+
+        final FieldVisitor superField(int access, String name, String desc, String signature, Object value) {
+            return super.visitField(access, name, desc, signature, value);
+        }
+
+        private final class FindInMethod extends MethodVisitor {
+
+            private final String name;
+            private final String desc;
+            private final int access;
+            private FindInAnno fia;
+            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
+                    found++;
+                    return new FindInAnno();
+                }
+                return super.visitAnnotation(desc, visible);
+            }
+
+            private void generateJSBody(FindInAnno fia) {
+                this.fia = fia;
+            }
+
+            @Override
+            public void visitCode() {
+                if (fia == null) {
+                    return;
+                }
+                generateBody(true);
+            }
+
+            private boolean generateBody(boolean hasCode) {
+                if (bodyGenerated) {
+                    return false;
+                }
+                bodyGenerated = true;
+                if (mv != null) {
+                    AnnotationVisitor va = super.visitAnnotation("Lnet/java/html/js/JavaScriptBody;", false);
+                    AnnotationVisitor varr = va.visitArray("args");
+                    for (String argName : fia.args) {
+                        varr.visit(null, argName);
+                    }
+                    varr.visitEnd();
+                    va.visit("javacall", fia.javacall);
+                    va.visit("body", fia.body);
+                    va.visitEnd();
+                }
+                
+                String body;
+                List<String> args;
+                if (fia.javacall) {
+                    body = callback(fia.body);
+                    args = new ArrayList<String>(fia.args);
+                    args.add("vm");
+                } else {
+                    body = fia.body;
+                    args = fia.args;
+                }
+
+                super.visitFieldInsn(
+                        Opcodes.GETSTATIC, FindInClass.this.name,
+                        "$$fn$$" + name + "_" + found,
+                        "Lorg/netbeans/html/boot/spi/Fn;"
+                );
+                super.visitInsn(Opcodes.DUP);
+                super.visitMethodInsn(
+                        Opcodes.INVOKESTATIC,
+                        "org/netbeans/html/boot/spi/Fn", "isValid",
+                        "(Lorg/netbeans/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.visitInsn(fia.keepAlive ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
+                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/netbeans/html/boot/spi/Fn", "define",
+                        "(Ljava/lang/Class;ZLjava/lang/String;[Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;"
+                );
+                Label noPresenter = new Label();
+                super.visitInsn(Opcodes.DUP);
+                super.visitJumpInsn(Opcodes.IFNULL, noPresenter);
+                if (resource != null) {
+                    super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
+                    super.visitLdcInsn(resource);
+                    super.visitMethodInsn(Opcodes.INVOKESTATIC,
+                            "org/netbeans/html/boot/spi/Fn", "preload",
+                            "(Lorg/netbeans/html/boot/spi/Fn;Ljava/lang/Class;Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;"
+                    );
+                }
+                super.visitInsn(Opcodes.DUP);
+                super.visitFieldInsn(
+                        Opcodes.PUTSTATIC, FindInClass.this.name,
+                        "$$fn$$" + name + "_" + found,
+                        "Lorg/netbeans/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) {
+                            return new SignatureVisitor(Opcodes.ASM4) {
+                                @Override
+                                public void visitClassType(String name) {
+                                    returnType = Type.getType("[" + Type.getObjectType(name).getDescriptor());
+                                }
+
+                                @Override
+                                public void visitBaseType(char descriptor) {
+                                    returnType = Type.getType("[" + descriptor);
+                                }
+                            };
+                        }
+                        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);
+                }
+
+                if (fia.wait4js) {
+                    super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+                            "org/netbeans/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: {
+                            Label handleNullValue = new Label();
+                            super.visitInsn(Opcodes.DUP);
+                            super.visitJumpInsn(Opcodes.IFNULL, handleNullValue);
+                            super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
+                            super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+                                    "java/lang/Boolean", "booleanValue", "()Z"
+                            );
+                            super.visitInsn(Opcodes.IRETURN);
+                            super.visitLabel(handleNullValue);
+                            super.visitInsn(Opcodes.ICONST_0);
+                            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));
+                    }
+                } else {
+                    super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+                            "org/netbeans/html/boot/spi/Fn", "invokeLater", "(Ljava/lang/Object;[Ljava/lang/Object;)V"
+                    );
+                    super.visitInsn(Opcodes.RETURN);
+                }
+                super.visitLabel(noPresenter);
+                if (hasCode) {
+                    super.visitCode();
+                } else {
+                    super.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException");
+                    super.visitInsn(Opcodes.DUP);
+                    super.visitLdcInsn("No presenter active. Use BrwsrCtx.execute!");
+                    super.visitMethodInsn(Opcodes.INVOKESPECIAL, 
+                        "java/lang/IllegalStateException", "<init>", "(Ljava/lang/String;)V"
+                    );
+                    this.visitInsn(Opcodes.ATHROW);
+                }
+                return true;
+            }
+            
+            @Override
+            public void visitEnd() {
+                super.visitEnd();
+                if (fia != null) {
+                    if (generateBody(false)) {
+                        // native method
+                        super.visitMaxs(1, 0);
+                    }
+                    FindInClass.this.superField(
+                            Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
+                            "$$fn$$" + name + "_" + found,
+                            "Lorg/netbeans/html/boot/spi/Fn;",
+                            null, null
+                    );
+                }
+            }
+
+            private final class FindInAnno extends AnnotationVisitor {
+
+                List<String> args = new ArrayList<String>();
+                String body;
+                boolean javacall = false;
+                boolean wait4js = true;
+                boolean keepAlive = true;
+
+                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;
+                    }
+                    if (name.equals("wait4js")) { // NOI18N
+                        wait4js = (Boolean) value;
+                        return;
+                    }
+                    if (name.equals("keepAlive")) { // NOI18N
+                        keepAlive = (Boolean) value;
+                        return;
+                    }
+                    assert name.equals("body"); // NOI18N
+                    body = (String) value;
+                }
+
+                @Override
+                public AnnotationVisitor visitArray(String name) {
+                    return this;
+                }
+
+                @Override
+                public void visitEnd() {
+                    if (body != null) {
+                        generateJSBody(this);
+                    }
+                }
+            }
+        }
+
+        private final class LoadResource extends AnnotationVisitor {
+            public LoadResource(AnnotationVisitor av) {
+                super(Opcodes.ASM4, av);
+            }
+
+            @Override
+            public void visit(String attrName, Object value) {
+                super.visit(attrName, value);
+                String relPath = (String) value;
+                if (relPath.startsWith("/")) {
+                    resource = relPath;
+                } else {
+                    int last = name.lastIndexOf('/');
+                    String fullPath = name.substring(0, last + 1) + relPath;
+                    resource = fullPath;
+                }
+            }
+        }
+    }
+
+    private static class ClassWriterEx extends ClassWriter {
+
+        private final 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 class JsClassLoaderImpl extends JsClassLoader {
+
+        private final FindResources f;
+        private final Fn.Presenter d;
+
+        public JsClassLoaderImpl(ClassLoader parent, FindResources f, Fn.Presenter d) {
+            super(parent);
+            setDefaultAssertionStatus(JsClassLoader.class.desiredAssertionStatus());
+            this.f = f;
+            this.d = d;
+        }
+
+        @Override
+        protected URL findResource(String name) {
+            List<URL> l = res(name, true);
+            return l.isEmpty() ? null : l.get(0);
+        }
+
+        @Override
+        protected Enumeration<URL> findResources(String name) {
+            return Collections.enumeration(res(name, false));
+        }
+        
+        private List<URL> res(String name, boolean oneIsEnough) {
+            List<URL> l = new ArrayList<URL>();
+            f.findResources(name, l, oneIsEnough);
+            return l;
+        }
+    
+        @Override
+        protected Class<?> findClass(String name) throws ClassNotFoundException {
+            if (name.startsWith("javafx")) {
+                return Class.forName(name);
+            }
+            if (name.startsWith("netscape")) {
+                return Class.forName(name);
+            }
+            if (name.startsWith("com.sun")) {
+                return Class.forName(name);
+            }
+            if (name.startsWith("org.netbeans.html.context.spi")) {
+                return Class.forName(name);
+            }
+            if (name.startsWith("net.java.html.BrwsrCtx")) {
+                return Class.forName(name);
+            }
+            if (name.equals(JsClassLoader.class.getName())) {
+                return JsClassLoader.class;
+            }
+            if (name.equals(Fn.class.getName())) {
+                return Fn.class;
+            }
+            if (name.equals(Fn.Presenter.class.getName())) {
+                return Fn.Presenter.class;
+            }
+            if (name.equals(Fn.ToJavaScript.class.getName())) {
+                return Fn.ToJavaScript.class;
+            }
+            if (name.equals(Fn.FromJavaScript.class.getName())) {
+                return Fn.FromJavaScript.class;
+            }
+            if (name.equals(FnUtils.class.getName())) {
+                return FnUtils.class;
+            }
+            if (
+                name.equals("org.netbeans.html.boot.spi.Fn") ||
+                name.equals("org.netbeans.html.boot.impl.FnUtils") ||
+                name.equals("org.netbeans.html.boot.impl.FnContext")
+            ) {
+                return Class.forName(name);
+            }
+            URL u = findResource(name.replace('.', '/') + ".class");
+            if (u != null) {
+                InputStream is = null;
+                try {
+                    is = u.openStream();
+                    byte[] arr = new byte[is.available()];
+                    int len = 0;
+                    while (len < arr.length) {
+                        int read = is.read(arr, len, arr.length - len);
+                        if (read == -1) {
+                            throw new IOException("Can't read " + u);
+                        }
+                        len += read;
+                    }
+                    is.close();
+                    is = null;
+                    if (JsPkgCache.process(this, name)) {
+                        arr = FnUtils.transform(arr, this);
+                    }
+                    return defineClass(name, arr, 0, arr.length);
+                } catch (IOException ex) {
+                    throw new ClassNotFoundException("Can't load " + name, ex);
+                } finally {
+                    try {
+                        if (is != null) is.close();
+                    } catch (IOException ex) {
+                        throw new ClassNotFoundException(null, ex);
+                    }
+                }
+            }
+            return super.findClass(name);
+        }
+    
+        protected Fn defineFn(String code, String... names) {
+            return d.defineFn(code, names);
+        }
+        
+        protected void loadScript(Reader code) throws Exception {
+            d.loadScript(code);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java
----------------------------------------------------------------------
diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java b/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java
new file mode 100644
index 0000000..ce9c4bb
--- /dev/null
+++ b/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java
@@ -0,0 +1,528 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.boot.impl;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Completion;
+import javax.annotation.processing.Completions;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+import net.java.html.js.JavaScriptBody;
+import net.java.html.js.JavaScriptResource;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+@ServiceProvider(service = Processor.class)
+public final class JavaScriptProcesor extends AbstractProcessor {
+    private final Map<String,Map<String,ExecutableElement>> javacalls =
+        new HashMap<String,Map<String,ExecutableElement>>();
+    private final Map<String,Set<TypeElement>> bodies =
+        new HashMap<String, Set<TypeElement>>();
+
+    @Override
+    public Set<String> getSupportedAnnotationTypes() {
+        Set<String> set = new HashSet<String>();
+        set.add(JavaScriptBody.class.getName());
+        set.add(JavaScriptResource.class.getName());
+        return set;
+    }
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+        final Messager msg = processingEnv.getMessager();
+        for (Element e : roundEnv.getElementsAnnotatedWith(JavaScriptBody.class)) {
+            if (e.getKind() != ElementKind.METHOD && e.getKind() != ElementKind.CONSTRUCTOR) {
+                continue;
+            }
+            ExecutableElement ee = (ExecutableElement)e;
+            List<? extends VariableElement> params = ee.getParameters();
+
+            JavaScriptBody jsb = e.getAnnotation(JavaScriptBody.class);
+            if (jsb == null) {
+                continue;
+            } else {
+                Set<TypeElement> classes = this.bodies.get(findPkg(e));
+                if (classes == null) {
+                    classes = new HashSet<TypeElement>();
+                    bodies.put(findPkg(e), classes);
+                }
+                Element t = e.getEnclosingElement();
+                while (!t.getKind().isClass() && !t.getKind().isInterface()) {
+                    t = t.getEnclosingElement();
+                }
+                classes.add((TypeElement)t);
+            }
+            String[] arr = jsb.args();
+            if (params.size() != arr.length) {
+                msg.printMessage(Diagnostic.Kind.ERROR, "Number of args arguments does not match real arguments!", e);
+            }
+            for (int i = 0; i < arr.length; i++) {
+                if (!params.get(i).getSimpleName().toString().equals(arr[i])) {
+                    msg.printMessage(Diagnostic.Kind.WARNING, "Actual method parameter names and args ones " + Arrays.toString(arr) + " differ", e);
+                }
+            }
+            if (!jsb.wait4js() && ee.getReturnType().getKind() != TypeKind.VOID) {
+                msg.printMessage(Diagnostic.Kind.ERROR, "Methods that don't wait for JavaScript to finish must return void!", e);
+            }
+            if (!jsb.javacall() && jsb.body().contains(".@")) {
+                msg.printMessage(Diagnostic.Kind.WARNING, "Usage of .@ usually requires javacall=true", e);
+            }
+            if (jsb.javacall()) {
+                JsCallback verify = new VerifyCallback(e);
+                try {
+                    verify.parse(jsb.body());
+                } catch (IllegalStateException ex) {
+                    msg.printMessage(Diagnostic.Kind.ERROR, ex.getLocalizedMessage(), e);
+                }
+            }
+        }
+        for (Element e : roundEnv.getElementsAnnotatedWith(JavaScriptResource.class)) {
+            JavaScriptResource r = e.getAnnotation(JavaScriptResource.class);
+            if (r == null) {
+                continue;
+            }
+            final String res;
+            if (r.value().startsWith("/")) {
+                res = r.value().substring(1);
+            } else {
+                res = findPkg(e).replace('.', '/') + "/" + r.value();
+            }
+
+            try {
+                FileObject os = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", res);
+                os.openInputStream().close();
+            } catch (IOException ex1) {
+                try {
+                    FileObject os2 = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", res);
+                    os2.openInputStream().close();
+                } catch (IOException ex2) {
+                    try {
+                        FileObject os3 = processingEnv.getFiler().getResource(StandardLocation.CLASS_PATH, "", res);
+                        os3.openInputStream().close();
+                    } catch (IOException ex3) {
+                        msg.printMessage(Diagnostic.Kind.ERROR, "Cannot find resource " + res, e);
+                    }
+                }
+            }
+
+            boolean found = false;
+            for (Element mthod : e.getEnclosedElements()) {
+                if (mthod.getKind() != ElementKind.METHOD) {
+                    continue;
+                }
+                if (mthod.getAnnotation(JavaScriptBody.class) != null) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                msg.printMessage(Diagnostic.Kind.ERROR, "At least one method needs @JavaScriptBody annotation. "
+                    + "Otherwise it is not guaranteed the resource will ever be loaded,", e
+                );
+            }
+        }
+
+        if (roundEnv.processingOver()) {
+            generateCallbackClass(javacalls);
+            generateJavaScriptBodyList(bodies);
+            javacalls.clear();
+        }
+        return true;
+    }
+
+    @Override
+    public Iterable<? extends Completion> getCompletions(Element e,
+        AnnotationMirror annotation, ExecutableElement member, String userText
+    ) {
+        StringBuilder sb = new StringBuilder();
+        if (e.getKind() == ElementKind.METHOD && member.getSimpleName().contentEquals("args")) {
+            ExecutableElement ee = (ExecutableElement) e;
+            String sep = "";
+            sb.append("{ ");
+            for (VariableElement ve : ee.getParameters()) {
+                sb.append(sep).append('"').append(ve.getSimpleName())
+                    .append('"');
+                sep = ", ";
+            }
+            sb.append(" }");
+            return Collections.nCopies(1, Completions.of(sb.toString()));
+        }
+        return null;
+    }
+
+    private class VerifyCallback extends JsCallback {
+        private final Element e;
+        public VerifyCallback(Element e) {
+            this.e = e;
+        }
+
+        @Override
+        protected CharSequence callMethod(String ident, String fqn, String method, String params) {
+            final TypeElement type = processingEnv.getElementUtils().getTypeElement(fqn);
+            if (type == null) {
+                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
+                    "Callback to non-existing class " + fqn, e
+                );
+                return "";
+            }
+            ExecutableElement found = null;
+            StringBuilder foundParams = new StringBuilder();
+            for (Element m : type.getEnclosedElements()) {
+                if (m.getKind() != ElementKind.METHOD) {
+                    continue;
+                }
+                if (m.getSimpleName().contentEquals(method)) {
+                    String paramTypes = findParamTypes((ExecutableElement)m);
+                    if (paramTypes.equals(params)) {
+                        found = (ExecutableElement) m;
+                        break;
+                    }
+                    foundParams.append(paramTypes).append("\n");
+                }
+            }
+            if (found == null) {
+                if (foundParams.length() == 0) {
+                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
+                        "Callback to class " + fqn + " with unknown method " + method, e
+                    );
+                } else {
+                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
+                        "Callback to " + fqn + "." + method + " with wrong parameters: " +
+                        params + ". Only known parameters are " + foundParams, e
+                    );
+                }
+            } else {
+                Map<String,ExecutableElement> mangledOnes = javacalls.get(findPkg(e));
+                if (mangledOnes == null) {
+                    mangledOnes = new TreeMap<String, ExecutableElement>();
+                    javacalls.put(findPkg(e), mangledOnes);
+                }
+                String mangled = JsCallback.mangle(fqn, method, findParamTypes(found));
+                mangledOnes.put(mangled, found);
+            }
+            return "";
+        }
+
+        private String findParamTypes(ExecutableElement method) {
+            ExecutableType t = (ExecutableType) method.asType();
+            StringBuilder sb = new StringBuilder();
+            sb.append('(');
+            for (TypeMirror tm : t.getParameterTypes()) {
+                if (tm.getKind().isPrimitive()) {
+                    switch (tm.getKind()) {
+                        case INT: sb.append('I'); break;
+                        case BOOLEAN: sb.append('Z'); break;
+                        case BYTE: sb.append('B'); break;
+                        case CHAR: sb.append('C'); break;
+                        case SHORT: sb.append('S'); break;
+                        case DOUBLE: sb.append('D'); break;
+                        case FLOAT: sb.append('F'); break;
+                        case LONG: sb.append('J'); break;
+                        default:
+                            throw new IllegalStateException("Uknown " + tm.getKind());
+                    }
+                } else {
+                    while (tm.getKind() == TypeKind.ARRAY) {
+                        sb.append('[');
+                        tm = ((ArrayType)tm).getComponentType();
+                    }
+                    sb.append('L');
+                    Types tu = processingEnv.getTypeUtils();
+                    Element elm = tu.asElement(tu.erasure(tm));
+                    dumpElems(sb, elm, ';');
+                }
+            }
+            sb.append(')');
+            return sb.toString();
+        }
+    }
+
+    private static void dumpElems(StringBuilder sb, Element e, char after) {
+        if (e == null) {
+            return;
+        }
+        if (e.getKind() == ElementKind.PACKAGE) {
+            PackageElement pe = (PackageElement) e;
+            sb.append(pe.getQualifiedName().toString().replace('.', '/')).append('/');
+            return;
+        }
+        Element p = e.getEnclosingElement();
+        dumpElems(sb, p, '$');
+        sb.append(e.getSimpleName());
+        sb.append(after);
+    }
+
+    private void generateJavaScriptBodyList(Map<String,Set<TypeElement>> bodies) {
+        if (bodies.isEmpty()) {
+            return;
+        }
+        try {
+            FileObject all = processingEnv.getFiler().createResource(
+                StandardLocation.CLASS_OUTPUT, "", "META-INF/net.java.html.js.classes"
+            );
+            PrintWriter wAll = new PrintWriter(new OutputStreamWriter(
+                all.openOutputStream(), "UTF-8"
+            ));
+            for (Map.Entry<String, Set<TypeElement>> entry : bodies.entrySet()) {
+                String pkg = entry.getKey();
+                Set<TypeElement> classes = entry.getValue();
+
+                FileObject out = processingEnv.getFiler().createResource(
+                    StandardLocation.CLASS_OUTPUT, pkg, "net.java.html.js.classes",
+                    classes.iterator().next()
+                );
+                OutputStream os = out.openOutputStream();
+                try {
+                    PrintWriter w = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
+                    for (TypeElement type : classes) {
+                        final Name bn = processingEnv.getElementUtils().getBinaryName(type);
+                        w.println(bn);
+                        wAll.println(bn);
+                    }
+                    w.flush();
+                    w.close();
+                } catch (IOException x) {
+                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to write to " + entry.getKey() + ": " + x.toString());
+                } finally {
+                    os.close();
+                }
+            }
+            wAll.close();
+        } catch (IOException x) {
+            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to write to " + "META-INF/net.java.html.js.classes: " + x.toString());
+        }
+    }
+
+    private void generateCallbackClass(Map<String,Map<String, ExecutableElement>> process) {
+        for (Map.Entry<String, Map<String, ExecutableElement>> pkgEn : process.entrySet()) {
+            String pkgName = pkgEn.getKey();
+            Map<String, ExecutableElement> map = pkgEn.getValue();
+            StringBuilder source = new StringBuilder();
+            source.append("package ").append(pkgName).append(";\n");
+            source.append("public final class $JsCallbacks$ {\n");
+            source.append("  static final $JsCallbacks$ VM = new $JsCallbacks$(null);\n");
+            source.append("  private final org.netbeans.html.boot.spi.Fn.Presenter p;\n");
+            source.append("  private $JsCallbacks$ last;\n");
+            source.append("  private $JsCallbacks$(org.netbeans.html.boot.spi.Fn.Presenter p) {\n");
+            source.append("    this.p = p;\n");
+            source.append("  }\n");
+            source.append("  final $JsCallbacks$ current() {\n");
+            source.append("    org.netbeans.html.boot.spi.Fn.Presenter now = org.netbeans.html.boot.spi.Fn.activePresenter();\n");
+            source.append("    if (now == p) return this;\n");
+            source.append("    if (last != null && now == last.p) return last;\n");
+            source.append("    return last = new $JsCallbacks$(now);\n");
+            source.append("  }\n");
+            for (Map.Entry<String, ExecutableElement> entry : map.entrySet()) {
+                final String mangled = entry.getKey();
+                final ExecutableElement m = entry.getValue();
+                generateMethod(false, m, source, mangled);
+                generateMethod(true, m, source, "raw$" + mangled);
+            }
+            source.append("}\n");
+            final String srcName = pkgName + ".$JsCallbacks$";
+            try {
+                Writer w = processingEnv.getFiler().createSourceFile(srcName,
+                    map.values().toArray(new Element[map.size()])
+                ).openWriter();
+                w.write(source.toString());
+                w.close();
+            } catch (IOException ex) {
+                processingEnv.getMessager().printMessage(
+                    Diagnostic.Kind.ERROR, "Can't write " + srcName + ": " + ex.getMessage()
+                );
+            }
+        }
+    }
+
+    private void generateMethod(boolean selfObj, final ExecutableElement m, StringBuilder source, final String mangled) {
+        final boolean isStatic = m.getModifiers().contains(Modifier.STATIC);
+        if (isStatic && selfObj) {
+            return;
+        }
+        final TypeElement selfType = (TypeElement)m.getEnclosingElement();
+        Types tu = processingEnv.getTypeUtils();
+
+        source.append("\n  public java.lang.Object ")
+                .append(mangled)
+                .append("(");
+
+        String sep = "";
+        StringBuilder convert = new StringBuilder();
+        if (!isStatic) {
+            if (selfObj) {
+                source.append("java.lang.Object self");
+                convert.append("    if (p instanceof org.netbeans.html.boot.spi.Fn.FromJavaScript) {\n");
+                convert.append("      self").
+                        append(" = ((org.netbeans.html.boot.spi.Fn.FromJavaScript)p).toJava(self").
+                        append(");\n");
+                convert.append("    }\n");
+            } else {
+                source.append(selfType.getQualifiedName());
+                source.append(" self");
+            }
+            sep = ", ";
+        }
+
+        int cnt = 0;
+        for (VariableElement ve : m.getParameters()) {
+            source.append(sep);
+            ++cnt;
+            final TypeMirror t = ve.asType();
+            if (!t.getKind().isPrimitive() && !"java.lang.String".equals(t.toString())) { // NOI18N
+                source.append("java.lang.Object");
+                convert.append("    if (p instanceof org.netbeans.html.boot.spi.Fn.FromJavaScript) {\n");
+                convert.append("      arg").append(cnt).
+                        append(" = ((org.netbeans.html.boot.spi.Fn.FromJavaScript)p).toJava(arg").append(cnt).
+                        append(");\n");
+                convert.append("    }\n");
+            } else {
+                source.append(t);
+            }
+            source.append(" arg").append(cnt);
+            sep = ", ";
+        }
+        source.append(") throws Throwable {\n");
+        source.append(convert);
+        if (useTryResources()) {
+            source.append("    try (java.io.Closeable a = org.netbeans.html.boot.spi.Fn.activate(p)) { \n");
+        } else {
+            source.append("    java.io.Closeable a = org.netbeans.html.boot.spi.Fn.activate(p); try {\n");
+        }
+        source.append("    ");
+        if (m.getReturnType().getKind() != TypeKind.VOID) {
+            source.append("java.lang.Object $ret = ");
+        }
+        if (isStatic) {
+            source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
+            source.append('.');
+        } else {
+            if (selfObj) {
+                source.append("((");
+                source.append(selfType.getQualifiedName());
+                source.append(")self).");
+            } else {
+                source.append("self.");
+            }
+        }
+        source.append(m.getSimpleName());
+        source.append("(");
+        cnt = 0;
+        sep = "";
+        for (VariableElement ve : m.getParameters()) {
+            source.append(sep);
+            source.append("(").append(tu.erasure(ve.asType()));
+            source.append(")arg").append(++cnt);
+            sep = ", ";
+        }
+        source.append(");\n");
+        if (m.getReturnType().getKind() == TypeKind.VOID) {
+            source.append("    return null;\n");
+        } else {
+            source.append("    if (p instanceof org.netbeans.html.boot.spi.Fn.ToJavaScript) {\n");
+            source.append("      $ret = ((org.netbeans.html.boot.spi.Fn.ToJavaScript)p).toJavaScript($ret);\n");
+            source.append("    }\n");
+            source.append("    return $ret;\n");
+        }
+        if (useTryResources()) {
+            source.append("    }\n");
+        } else {
+
+            source.append("    } finally {\n");
+            source.append("      a.close();\n");
+            source.append("    }\n");
+        }
+        source.append("  }\n");
+    }
+
+    private boolean useTryResources() {
+        try {
+            return processingEnv.getSourceVersion().compareTo(SourceVersion.RELEASE_7) >= 0;
+        } catch (LinkageError err) {
+            // can happen when running on JDK6
+            return false;
+        }
+    }
+
+    private static String findPkg(Element e) {
+        while (e.getKind() != ElementKind.PACKAGE) {
+            e = e.getEnclosingElement();
+        }
+        return ((PackageElement)e).getQualifiedName().toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java
----------------------------------------------------------------------
diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java b/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java
new file mode 100644
index 0000000..5d72fa2
--- /dev/null
+++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsAgent.java
@@ -0,0 +1,75 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.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 premain(String args, Instrumentation instr) {
+        instr.addTransformer(new JsAgent());
+    }
+    
+    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 {
+            if (JsPkgCache.process(loader, className)) {
+                return FnUtils.transform(classfileBuffer, loader);
+            } else {
+                return classfileBuffer;
+            }
+        } catch (Exception ex) {
+            return classfileBuffer;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/impl/JsCallback.java
----------------------------------------------------------------------
diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JsCallback.java b/boot/src/main/java/org/netbeans/html/boot/impl/JsCallback.java
new file mode 100644
index 0000000..45bf53b
--- /dev/null
+++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsCallback.java
@@ -0,0 +1,160 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.boot.impl;
+
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+abstract class JsCallback {
+    final String parse(String body) {
+        StringBuilder sb = new StringBuilder();
+        int pos = 0;
+        for (;;) {
+            int next = body.indexOf(".@", pos);
+            if (next == -1) {
+                sb.append(body.substring(pos));
+                body = sb.toString();
+                break;
+            }
+            int ident = next;
+            while (ident > 0) {
+                if (!Character.isJavaIdentifierPart(body.charAt(--ident))) {
+                    ident++;
+                    break;
+                }
+            }
+            String refId = body.substring(ident, next);
+            
+            sb.append(body.substring(pos, ident));
+            
+            int sigBeg = body.indexOf('(', next);
+            int sigEnd = body.indexOf(')', sigBeg);
+            int colon4 = body.indexOf("::", next);
+            if (sigBeg == -1 || sigEnd == -1 || colon4 == -1) {
+                throw new IllegalStateException(
+                    "Wrong format of instance callback. "
+                    + "Should be: 'inst.@pkg.Class::method(Ljava/lang/Object;)(param)':\n" 
+                    + body
+                );
+            }
+            String fqn = body.substring(next + 2, colon4);
+            String method = body.substring(colon4 + 2, sigBeg);
+            String params = body.substring(sigBeg, sigEnd + 1);
+
+            int paramBeg = body.indexOf('(', sigEnd + 1);
+            if (paramBeg == -1) {
+                throw new IllegalStateException(
+                    "Wrong format of instance callback. "
+                    + "Should be: 'inst.@pkg.Class::method(Ljava/lang/Object;)(param)':\n" 
+                    + body
+                );
+            }
+            
+            sb.append(callMethod(refId, fqn, method, params));
+            if (body.charAt(paramBeg + 1) != (')')) {
+                sb.append(",");
+            }
+            pos = paramBeg + 1;
+        }
+        pos = 0;
+        sb = null;
+        for (;;) {
+            int next = body.indexOf("@", pos);
+            if (next == -1) {
+                if (sb == null) {
+                    return body;
+                }
+                sb.append(body.substring(pos));
+                return sb.toString();
+            }
+            if (sb == null) {
+                sb = new StringBuilder();
+            }
+            
+            sb.append(body.substring(pos, next));
+            
+            int sigBeg = body.indexOf('(', next);
+            int sigEnd = body.indexOf(')', sigBeg);
+            int colon4 = body.indexOf("::", next);
+            int paramBeg = body.indexOf('(', sigEnd + 1);
+            if (sigBeg == -1 || sigEnd == -1 || colon4 == -1 || paramBeg == -1) {
+                throw new IllegalStateException(
+                    "Wrong format of static callback. "
+                    + "Should be: '@pkg.Class::staticMethod(Ljava/lang/Object;)(param)':\n" 
+                    + body
+                );
+            }
+            String fqn = body.substring(next + 1, colon4);
+            String method = body.substring(colon4 + 2, sigBeg);
+            String params = body.substring(sigBeg, sigEnd + 1);
+
+            
+            sb.append(callMethod(null, fqn, method, params));
+            pos = paramBeg + 1;
+        }
+    }
+
+    protected abstract CharSequence callMethod(
+        String ident, String fqn, String method, String params
+    );
+
+    static String mangle(String fqn, String method, String params) {
+        if (params.startsWith("(")) {
+            params = params.substring(1);
+        }
+        if (params.endsWith(")")) {
+            params = params.substring(0, params.length() - 1);
+        }
+        return 
+            replace(fqn) + "$" + replace(method) + "$" + replace(params);
+    }
+    
+    private static String replace(String orig) {
+        return orig.replace("_", "_1").
+            replace(";", "_2").
+            replace("[", "_3").
+            replace('.', '_').replace('/', '_');
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java
----------------------------------------------------------------------
diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java b/boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java
new file mode 100644
index 0000000..58b0a62
--- /dev/null
+++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java
@@ -0,0 +1,57 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.boot.impl;
+
+import net.java.html.js.JavaScriptBody;
+
+/** Marker class to help us recognize we assigned classloader is
+ * capable to handle {@link JavaScriptBody} annotated methods.
+ *
+ * @author Jaroslav Tulach
+ */
+abstract class JsClassLoader extends ClassLoader {
+    JsClassLoader(ClassLoader parent) {
+        super(parent);
+        setDefaultAssertionStatus(JsClassLoader.class.desiredAssertionStatus());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java
----------------------------------------------------------------------
diff --git a/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java b/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java
new file mode 100644
index 0000000..7064d57
--- /dev/null
+++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsPkgCache.java
@@ -0,0 +1,132 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.boot.impl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.WeakHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+final class JsPkgCache {
+    private final Map<String,Set<String>> props = new WeakHashMap<String, Set<String>>();
+    private static final Map<ClassLoader, JsPkgCache> CACHE = new WeakHashMap<ClassLoader, JsPkgCache>();
+    private static final Set<String> NONE = Collections.emptySet();
+
+    public static boolean process(ClassLoader l, String className) {
+        if (className.equals("org.netbeans.html.boot.impl.Test")) { // NOI18N
+            return true;
+        }
+        Set<String> p;
+        JsPkgCache c;
+        String pkgName;
+        synchronized (CACHE) {
+            c = CACHE.get(l);
+            if (c == null) {
+                c = new JsPkgCache();
+                CACHE.put(l, c);
+            }
+            int lastDot = className.lastIndexOf('.');
+            pkgName = className.substring(0, lastDot + 1).replace('.', '/');
+            p = c.props.get(pkgName);
+            if (p == NONE) {
+                return false;
+            } else if (p != null) {
+                return p.contains(className);
+            }
+        }
+        final String res = pkgName + "net.java.html.js.classes";
+        
+        Enumeration<URL> en;
+        try {
+            en = l.getResources(res);
+        } catch (IOException ex) {
+            en = null;
+        }
+        if (en == null || !en.hasMoreElements()) synchronized (CACHE) {
+            c.props.put(pkgName, NONE);
+            return false;
+        }
+
+        try {
+            Set<String> arr = new TreeSet<String>();
+            while (en.hasMoreElements()) {
+                URL u = en.nextElement();
+                BufferedReader r = new BufferedReader(
+                    new InputStreamReader(u.openStream(), "UTF-8")
+                );
+                for (;;) {
+                    String line = r.readLine();
+                    if (line == null) {
+                        break;
+                    }
+                    arr.add(line);
+                }
+                r.close();
+            }
+            p = arr;
+        } catch (IOException ex) {
+            LOG.log(Level.WARNING, "Can't read " + res, ex);
+            p = NONE;
+        }
+        
+        synchronized (CACHE) {
+            c.props.put(pkgName, p);
+            return p.contains(className);
+        }
+        
+    }
+    private static final Logger LOG = Logger.getLogger(JsPkgCache.class.getName());
+}