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:51 UTC

[18/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/spi/Fn.java
----------------------------------------------------------------------
diff --git a/boot/src/main/java/org/netbeans/html/boot/spi/Fn.java b/boot/src/main/java/org/netbeans/html/boot/spi/Fn.java
new file mode 100644
index 0000000..c6deffb
--- /dev/null
+++ b/boot/src/main/java/org/netbeans/html/boot/spi/Fn.java
@@ -0,0 +1,388 @@
+/**
+ * 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.spi;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import net.java.html.js.JavaScriptBody;
+import org.netbeans.html.boot.impl.FnContext;
+
+/** Represents single JavaScript function that can be invoked. 
+ * Created via {@link Presenter#defineFn(java.lang.String, java.lang.String...)}.
+ *
+ * @author Jaroslav Tulach
+ */
+public abstract class Fn {
+    private final Presenter presenter;
+    
+    /**
+     * @deprecated Ineffective as of 0.6. 
+     * Provide a presenter via {@link #Fn(org.netbeans.html.boot.spi.Fn.Presenter)}
+     * constructor
+     */
+    @Deprecated
+    protected Fn() {
+        this(null);
+    }
+    
+    /** Creates new function object and associates it with given presenter.
+     * 
+     * @param presenter the browser presenter associated with this function
+     * @since 0.6 
+     */
+    protected Fn(Presenter presenter) {
+        this.presenter = presenter;
+    }
+
+    /** True, if currently active presenter is the same as presenter this
+     * function has been created for via {@link #Fn(org.netbeans.html.boot.spi.Fn.Presenter)}.
+     * 
+     * @return true, if proper presenter is used
+     */
+    public final boolean isValid() {
+        return presenter != null && FnContext.currentPresenter(false) == presenter;
+    }
+    
+    /** Helper method to check if the provided instance is valid function.
+     * Checks if the parameter is non-null and if so, does {@link #isValid()}
+     * check.
+     * 
+     * @param fnOrNull function or <code>null</code>
+     * @return true if the parameter is non-null and valid
+     * @since 0.7
+     */
+    public static boolean isValid(Fn fnOrNull) {
+        return fnOrNull != null && fnOrNull.isValid();
+    }
+
+    /** Helper method to find current presenter and ask it to define new
+     * function by calling {@link Presenter#defineFn(java.lang.String, java.lang.String...)}.
+     * 
+     * @param caller the class who wishes to define the function
+     * @param code the body of the function (can reference <code>this</code> and <code>names</code> variables)
+     * @param names names of individual parameters
+     * @return the function object that can be {@link Fn#invoke(java.lang.Object, java.lang.Object...) invoked}
+     *    - can return <code>null</code> if there is {@link #activePresenter() no presenter}
+     * @since 0.7
+     */
+    public static Fn define(Class<?> caller, String code, String... names) {
+        return define(caller, false, code, names);
+    }
+
+    /** Helper method to find current presenter and ask it to define new
+     * function.
+     * 
+     * @param caller the class who wishes to define the function
+     * @param keepParametersAlive whether Java parameters should survive in JavaScript
+     *   after the method invocation is over
+     * @param code the body of the function (can reference <code>this</code> and <code>names</code> variables)
+     * @param names names of individual parameters
+     * @return the function object that can be {@link Fn#invoke(java.lang.Object, java.lang.Object...) invoked}
+     *    - can return <code>null</code> if there is {@link #activePresenter() no presenter}
+     * @since 1.1
+     */
+    public static Fn define(Class<?> caller, boolean keepParametersAlive, String code, String... names) {
+        final Presenter p = FnContext.currentPresenter(false);
+        if (p == null) {
+            return null;
+        }
+        if (p instanceof KeepAlive) {
+            boolean[] arr;
+            if (!keepParametersAlive && names.length > 0) {
+                arr = new boolean[names.length];
+                for (int i = 0; i < arr.length; i++) {
+                    arr[i] = false;
+                }
+            } else {
+                arr = null;
+            }
+            return ((KeepAlive)p).defineFn(code, names, arr);
+        }
+        return p.defineFn(code, names);
+    }
+    
+    private static final Map<String,Set<Presenter>> LOADED = new HashMap<String, Set<Presenter>>();
+    
+    /** Wraps function to ensure that the script represented by <code>resource</code>
+     * gets loaded into the browser environment before the function <code>fn</code>
+     * is executed.
+     * 
+     * @param fn original function to call (if <code>null</code> returns <code>null</code>)
+     * @param caller the class who wishes to define/call the function
+     * @param resource resources (accessible via {@link ClassLoader#getResource(java.lang.String)}) 
+     *   with a <em>JavaScript</em> that is supposed to loaded into the browser
+     *   environment
+     * @return function that ensures the script is loaded and then delegates
+     *   to <code>fn</code>. Returns <code>null</code> if the input <code>fn</code> is null
+     * @since 0.7
+     */
+    public static Fn preload(final Fn fn, final Class<?> caller, final String resource) {
+        if (fn == null) {
+            return null;
+        }
+        return new Fn(fn.presenter()) {
+            @Override
+            public Object invoke(Object thiz, Object... args) throws Exception {
+                loadResource();
+                return fn.invoke(thiz, args);
+            }
+
+            @Override
+            public void invokeLater(Object thiz, Object... args) throws Exception {
+                loadResource();
+                fn.invokeLater(thiz, args);
+            }
+            
+            private void loadResource() throws Exception {
+                Presenter p = presenter();
+                if (p == null) {
+                    p = FnContext.currentPresenter(false);
+                }
+                if (p != null) {
+                    Set<Presenter> there = LOADED.get(resource);
+                    if (there == null) {
+                        there = new HashSet<Presenter>();
+                        LOADED.put(resource, there);
+                    }
+                    if (there.add(p)) {
+                        final ClassLoader l = caller.getClassLoader();
+                        InputStream is = l.getResourceAsStream(resource);
+                        if (is == null && resource.startsWith("/")) {
+                            is = l.getResourceAsStream(resource.substring(1));
+                        }
+                        if (is == null) {
+                            throw new IOException("Cannot find " + resource + " in " + l);
+                        }
+                        try {
+                            InputStreamReader r = new InputStreamReader(is, "UTF-8");
+                            p.loadScript(r);
+                        } finally {
+                            is.close();
+                        }
+                    }
+                }
+            }
+        };
+    }
+
+    
+    /** The currently active presenter.
+     * 
+     * @return the currently active presenter or <code>null</code>
+     * @since 0.7
+     */
+    public static Presenter activePresenter() {
+        return FnContext.currentPresenter(false);
+    }
+    
+    /** Activates given presenter. Used to associate the native 
+     * JavaScript code specified by 
+     * {@link JavaScriptBody} annotation with certain presenter:
+     * <pre>
+     * try ({@link Closeable} c = Fn.activate(presenter)) {
+     *   doCallsInPresenterContext();
+     * }
+     * </pre>
+     * 
+     * @param p the presenter that should be active until closable is closed
+     * @return the closable to close
+     * @since 0.7
+     */
+    public static Closeable activate(Presenter p) {
+        return FnContext.activate(p);
+    }
+    
+    /** Invokes the defined function with specified <code>this</code> and
+     * appropriate arguments.
+     * 
+     * @param thiz the meaning of <code>this</code> inside of the JavaScript
+     *   function - can be <code>null</code>
+     * @param args arguments for the function
+     * @return return value from the function
+     * @throws Exception if something goes wrong, as exception may be thrown
+     */
+    public abstract Object invoke(Object thiz, Object... args) throws Exception;
+
+    /** Invokes the defined function with specified <code>this</code> and
+     * appropriate arguments asynchronously. The invocation may be 
+     * happen <em>"later"</em>.
+     * 
+     * @param thiz the meaning of <code>this</code> inside of the JavaScript
+     *   function - can be <code>null</code>
+     * @param args arguments for the function
+     * @throws Exception if something goes wrong, as exception may be thrown
+     * @since 0.7.6
+     */
+    public void invokeLater(Object thiz, Object... args) throws Exception {
+        invoke(thiz, args);
+    }
+    
+    /** Provides the function implementation access to the presenter provided
+     * in {@link #Fn(org.netbeans.html.boot.spi.Fn.Presenter) the constructor}.
+     * 
+     * @return presenter passed in the constructor (may be, but should not be <code>null</code>)
+     * @since 0.7
+     */
+    protected final Presenter presenter() {
+        return presenter;
+    }
+    
+    /** The representation of a <em>presenter</em> - usually a browser window.
+     * Should be provided by a library included in the application and registered
+     * in <code>META-INF/services</code>, for example with
+     * <code>@ServiceProvider(service = Fn.Presenter.class)</code> annotation.
+     * <p>
+     * Since 0.7 a presenter may implement {@link Executor} interface, in case
+     * it supports single threaded execution environment. The executor's
+     * {@link Executor#execute(java.lang.Runnable)} method is then supposed
+     * to invoke the runnable immediately (in case we are on the right thread
+     * already) or return and asynchronously invoke the runnable later on the
+     * right thread (if we are on wrong thread).
+     */
+    public interface Presenter {
+        /** Creates new function with given parameter names and provided body.
+         * 
+         * @param code the body of the function. Can refer to variables named
+         *   as <code>names</code>
+         * @param names names of parameters of the function - these will be 
+         *   available when the <code>code</code> body executes
+         * 
+         * @return function that can be later invoked
+         */
+        public Fn defineFn(String code, String... names);
+        
+        /** Opens the browser, loads provided page and when the
+         * page is ready, it calls back to the provider runnable.
+         * 
+         * @param page the URL for the page to display
+         * @param onPageLoad callback when the page is ready
+         */
+        public void displayPage(URL page, Runnable onPageLoad);
+        
+        /** Loads a script into the browser JavaScript interpreter and 
+         * executes it.
+         * @param code the script to execute
+         * @throws Exception if something goes wrong, throw an exception
+         */
+        public void loadScript(Reader code) throws Exception;
+    }
+    
+    /** Additional interface to be implemented by {@link Presenter}s that
+     * wish to control what objects are passed into the JavaScript virtual 
+     * machine.
+     * <p>
+     * If a JavaScript engine makes callback to Java method that returns 
+     * a value, the {@link #toJavaScript(java.lang.Object)} method is
+     * consulted to convert the Java value to something reasonable inside
+     * JavaScript VM.
+     * <p>
+     * <em>Note:</em> The implementation based on <em>JavaFX</em> <code>WebView</code>
+     * uses this interface to convert Java arrays to JavaScript ones.
+     * 
+     * @see Presenter
+     * @since 0.7
+     */
+    public interface ToJavaScript {
+        /** Convert a Java return value into some object suitable for
+         * JavaScript virtual machine.
+         * 
+         * @param toReturn the Java object to be returned
+         * @return the replacement value to return instead
+         */
+        public Object toJavaScript(Object toReturn);
+    }
+    
+    /** Additional interface to be implemented by {@link Presenter}s that
+     * need to convert JavaScript object (usually array) to Java object 
+     * when calling back from JavaScript to Java.
+     * <p>
+     * <em>Note:</em> The implementation based on <em>JavaFX</em>
+     * <code>WebView</code> uses this interface to convert JavaScript arrays to
+     * Java ones.
+      * 
+     * @since 0.7
+     */
+    public interface FromJavaScript {
+        /** Convert a JavaScript object into suitable Java representation
+         * before a Java method is called with this object as an argument.
+         * 
+         * @param js the JavaScript object
+         * @return replacement object for 
+         */
+        public Object toJava(Object js);
+    }
+
+    /** Additional interface to {@link Presenter} to control more precisely
+     * garbage collection behavior of individual parameters. See 
+     * {@link JavaScriptBody#keepAlive()} attribute for description of the
+     * actual behavior of the interface.
+     * 
+     * @since 1.1
+     */
+    public interface KeepAlive {
+        /** Creates new function with given parameter names and provided body.
+         * 
+         * @param code the body of the function. Can refer to variables named
+         *   as <code>names</code>
+         * @param names names of parameters of the function - these will be 
+         *   available when the <code>code</code> body executes
+         * @param keepAlive array of booleans describing for each parameter
+         *   whether it should be kept alive or not. Length of the array
+         *   must be the same as length of <code>names</code> array. The
+         *   array may be <code>null</code> to signal that all parameters
+         *   should be <em>kept alive</em>.
+         * 
+         * @return function that can be later invoked
+         */
+        public Fn defineFn(String code, String[] names, boolean[] keepAlive);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/java/org/netbeans/html/boot/spi/package.html
----------------------------------------------------------------------
diff --git a/boot/src/main/java/org/netbeans/html/boot/spi/package.html b/boot/src/main/java/org/netbeans/html/boot/spi/package.html
new file mode 100644
index 0000000..d4ef66e
--- /dev/null
+++ b/boot/src/main/java/org/netbeans/html/boot/spi/package.html
@@ -0,0 +1,54 @@
+<!--
+
+    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.
+
+-->
+<!DOCTYPE html>
+<html>
+    <body>
+        <div>Interfaces for integrators of various execution environments.</div>
+        Not really interesting for clients. The clients should rather use
+        {@link net.java.html.boot.BrowserBuilder} to launch their applications,
+        or (if they need to do some JavaScript calls themselves) look at
+        {@link net.java.html.js.JavaScriptBody} annotation and its usage.
+    </body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/main/resources/net/java/html/boot/html4j.txt
----------------------------------------------------------------------
diff --git a/boot/src/main/resources/net/java/html/boot/html4j.txt b/boot/src/main/resources/net/java/html/boot/html4j.txt
new file mode 100644
index 0000000..0552164
--- /dev/null
+++ b/boot/src/main/resources/net/java/html/boot/html4j.txt
@@ -0,0 +1,43 @@
+====
+    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.
+====
+

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/test/java/net/java/html/boot/BrowserBuilderTest.java
----------------------------------------------------------------------
diff --git a/boot/src/test/java/net/java/html/boot/BrowserBuilderTest.java b/boot/src/test/java/net/java/html/boot/BrowserBuilderTest.java
new file mode 100644
index 0000000..c4644fa
--- /dev/null
+++ b/boot/src/test/java/net/java/html/boot/BrowserBuilderTest.java
@@ -0,0 +1,134 @@
+/**
+ * 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 net.java.html.boot;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Locale;
+import static org.testng.Assert.*;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public class BrowserBuilderTest {
+    private File dir;
+    private File index;
+    
+    public BrowserBuilderTest() {
+    }
+    
+    @BeforeMethod public void prepareFiles() throws IOException {
+        dir = File.createTempFile("test", ".dir");
+        dir.delete();
+        assertTrue(dir.mkdirs(), "Dir successfully created: " + dir);
+        
+        index = new File(dir, "index.html");
+        index.createNewFile();
+        
+        System.setProperty("browser.rootdir", dir.getPath());
+    }
+    
+    @AfterMethod public void clearFiles() throws IOException {
+        Files.walkFileTree(dir.toPath(), new FileVisitor<Path>() {
+            @Override
+            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                Files.delete(file);
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                Files.delete(dir);
+                return FileVisitResult.CONTINUE;
+            }
+        });
+    }
+    
+
+    @Test public void findsZhCN() throws IOException {
+        File zh = new File(dir, "index_zh.html"); zh.createNewFile();
+        File zhCN = new File(dir, "index_zh_CN.html"); zhCN.createNewFile();
+        
+        IOException[] mal = { null };
+        URL url = BrowserBuilder.findLocalizedResourceURL("index.html", Locale.SIMPLIFIED_CHINESE, mal, BrowserBuilder.class);
+        
+        assertEquals(url, zhCN.toURI().toURL(), "Found both suffixes");
+    }
+    
+    @Test public void findsZh() throws IOException {
+        File zh = new File(dir, "index_zh.html"); zh.createNewFile();
+        
+        IOException[] mal = { null };
+        URL url = BrowserBuilder.findLocalizedResourceURL("index.html", Locale.SIMPLIFIED_CHINESE, mal, BrowserBuilder.class);
+        
+        assertEquals(url, zh.toURI().toURL(), "Found one suffix");
+    }
+
+    @Test public void findsIndex() throws IOException {
+        IOException[] mal = { null };
+        URL url = BrowserBuilder.findLocalizedResourceURL("index.html", Locale.SIMPLIFIED_CHINESE, mal, BrowserBuilder.class);
+        
+        assertEquals(url, index.toURI().toURL(), "Found root file");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/test/java/org/netbeans/html/boot/impl/Arithm.java
----------------------------------------------------------------------
diff --git a/boot/src/test/java/org/netbeans/html/boot/impl/Arithm.java b/boot/src/test/java/org/netbeans/html/boot/impl/Arithm.java
new file mode 100644
index 0000000..c4a3cd3
--- /dev/null
+++ b/boot/src/test/java/org/netbeans/html/boot/impl/Arithm.java
@@ -0,0 +1,53 @@
+/**
+ * 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
+ */
+public class Arithm {
+    public int sumTwo(int a, int b) {
+        return a + b;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/test/java/org/netbeans/html/boot/impl/Compile.java
----------------------------------------------------------------------
diff --git a/boot/src/test/java/org/netbeans/html/boot/impl/Compile.java b/boot/src/test/java/org/netbeans/html/boot/impl/Compile.java
new file mode 100644
index 0000000..6862e6a
--- /dev/null
+++ b/boot/src/test/java/org/netbeans/html/boot/impl/Compile.java
@@ -0,0 +1,307 @@
+/**
+ * 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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticListener;
+import javax.tools.FileObject;
+import javax.tools.ForwardingJavaFileManager;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.fail;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+final class Compile implements DiagnosticListener<JavaFileObject> {
+    private final List<Diagnostic<? extends JavaFileObject>> errors = 
+            new ArrayList<Diagnostic<? extends JavaFileObject>>();
+    private final Map<String, byte[]> classes;
+    private final String pkg;
+    private final String cls;
+    private final String html;
+    private final String sourceLevel;
+
+    private Compile(String html, String code, String sl) throws IOException {
+        this.pkg = findPkg(code);
+        this.cls = findCls(code);
+        this.html = html;
+        this.sourceLevel = sl;
+        classes = compile(html, code);
+    }
+
+    /** Performs compilation of given HTML page and associated Java code
+     */
+    public static Compile create(String html, String code) throws IOException {
+        return create(html, code, "1.7");
+    }
+    static Compile create(String html, String code, String sourceLevel) throws IOException {
+        return new Compile(html, code, sourceLevel);
+    }
+    
+    /** Checks for given class among compiled resources */
+    public byte[] get(String res) {
+        return classes.get(res);
+    }
+    
+    /** Obtains errors created during compilation.
+     */
+    public List<Diagnostic<? extends JavaFileObject>> getErrors() {
+        return getDiagnostics(Diagnostic.Kind.ERROR);
+    }
+    public List<Diagnostic<? extends JavaFileObject>> getDiagnostics(Diagnostic.Kind kind) {
+        List<Diagnostic<? extends JavaFileObject>> err;
+        err = new ArrayList<Diagnostic<? extends JavaFileObject>>();
+        for (Diagnostic<? extends JavaFileObject> diagnostic : errors) {
+            if (diagnostic.getKind() == kind) {
+                err.add(diagnostic);
+            }
+        }
+        return err;
+    }
+    
+    private Map<String, byte[]> compile(final String html, final String code) throws IOException {
+        StandardJavaFileManager sjfm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(this, null, null);
+
+        final Map<String, ByteArrayOutputStream> class2BAOS;
+        class2BAOS = new HashMap<String, ByteArrayOutputStream>();
+
+        JavaFileObject file = new SimpleJavaFileObject(URI.create("mem://mem"), Kind.SOURCE) {
+            @Override
+            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+                return code;
+            }
+        };
+        final JavaFileObject htmlFile = new SimpleJavaFileObject(URI.create("mem://mem2"), Kind.OTHER) {
+            @Override
+            public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+                return html;
+            }
+
+            @Override
+            public InputStream openInputStream() throws IOException {
+                return new ByteArrayInputStream(html.getBytes());
+            }
+        };
+        
+        final URI scratch;
+        try {
+            scratch = new URI("mem://mem3");
+        } catch (URISyntaxException ex) {
+            throw new IOException(ex);
+        }
+        
+        JavaFileManager jfm = new ForwardingJavaFileManager<JavaFileManager>(sjfm) {
+            @Override
+            public FileObject getFileForOutput(JavaFileManager.Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
+                try {
+                    return new VirtFO(new URI("mem://resource/" + relativeName), Kind.OTHER, relativeName);
+                } catch (URISyntaxException ex) {
+                    throw new IllegalStateException(ex);
+                }
+            }
+            
+            
+            @Override
+            public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
+                if (kind  == Kind.CLASS) {
+                    final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+                    class2BAOS.put(className.replace('.', '/') + ".class", buffer);
+                    return new SimpleJavaFileObject(sibling.toUri(), kind) {
+                        @Override
+                        public OutputStream openOutputStream() throws IOException {
+                            return buffer;
+                        }
+                    };
+                }
+                
+                if (kind == Kind.SOURCE) {
+                    final String n = className.replace('.', '/') + ".java";
+                    final URI un;
+                    try {
+                        un = new URI("mem://" + n);
+                    } catch (URISyntaxException ex) {
+                        throw new IOException(ex);
+                    }
+                    return new VirtFO(un/*sibling.toUri()*/, kind, n);
+                }
+                
+                throw new IllegalStateException();
+            }
+
+            @Override
+            public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
+                if (location == StandardLocation.SOURCE_PATH) {
+                    if (packageName.equals(pkg)) {
+                        return htmlFile;
+                    }
+                    if (packageName.isEmpty() && relativeName.startsWith(pkg.replace('.', '/'))) {
+                        return htmlFile;
+                    }
+                }
+                
+                return null;
+            }
+
+            @Override
+            public boolean isSameFile(FileObject a, FileObject b) {
+                if (a instanceof VirtFO && b instanceof VirtFO) {
+                    return ((VirtFO)a).getName().equals(((VirtFO)b).getName());
+                }
+                
+                return super.isSameFile(a, b);
+            }
+
+            class VirtFO extends SimpleJavaFileObject {
+
+                private final String n;
+
+                public VirtFO(URI uri, Kind kind, String n) {
+                    super(uri, kind);
+                    this.n = n;
+                }
+                private final ByteArrayOutputStream data = new ByteArrayOutputStream();
+
+                @Override
+                public OutputStream openOutputStream() throws IOException {
+                    return data;
+                }
+
+                @Override
+                public String getName() {
+                    return n;
+                }
+
+                @Override
+                public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+                    data.close();
+                    return new String(data.toByteArray());
+                }
+            }
+        };
+
+        ToolProvider.getSystemJavaCompiler().getTask(null, jfm, this, /*XXX:*/Arrays.asList("-source", sourceLevel, "-target", "1.7"), null, Arrays.asList(file)).call();
+
+        Map<String, byte[]> result = new HashMap<String, byte[]>();
+
+        for (Map.Entry<String, ByteArrayOutputStream> e : class2BAOS.entrySet()) {
+            result.put(e.getKey(), e.getValue().toByteArray());
+        }
+
+        return result;
+    }
+
+
+    @Override
+    public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
+        errors.add(diagnostic);
+    }
+    private static String findPkg(String java) throws IOException {
+        Pattern p = Pattern.compile("package\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}*;", Pattern.MULTILINE);
+        Matcher m = p.matcher(java);
+        if (!m.find()) {
+            throw new IOException("Can't find package declaration in the java file");
+        }
+        String pkg = m.group(1);
+        return pkg;
+    }
+    private static String findCls(String java) throws IOException {
+        Pattern p = Pattern.compile("class\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}", Pattern.MULTILINE);
+        Matcher m = p.matcher(java);
+        if (!m.find()) {
+            throw new IOException("Can't find package declaration in the java file");
+        }
+        String cls = m.group(1);
+        return cls;
+    }
+
+    String getHtml() {
+        String fqn = "'" + pkg + '.' + cls + "'";
+        return html.replace("'${fqn}'", fqn);
+    }
+    void assertErrors() {
+        assertFalse(getErrors().isEmpty(), "There are supposed to be some errors");
+    }
+
+    void assertError(String expMsg) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Can't find ").append(expMsg).append(" among:");
+        for (Diagnostic<? extends JavaFileObject> e : errors) {
+            String msg = e.getMessage(Locale.US);
+            if (msg.contains(expMsg)) {
+                return;
+            }
+            sb.append("\n");
+            sb.append(msg);
+        }
+        fail(sb.toString());
+    }
+
+    void assertNoErrors() {
+        assertTrue(getErrors().isEmpty(), "No errors expected: " + getErrors());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/test/java/org/netbeans/html/boot/impl/CountFnCreationTest.java
----------------------------------------------------------------------
diff --git a/boot/src/test/java/org/netbeans/html/boot/impl/CountFnCreationTest.java b/boot/src/test/java/org/netbeans/html/boot/impl/CountFnCreationTest.java
new file mode 100644
index 0000000..8e3f0e2
--- /dev/null
+++ b/boot/src/test/java/org/netbeans/html/boot/impl/CountFnCreationTest.java
@@ -0,0 +1,123 @@
+/**
+ * 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.Closeable;
+import java.io.IOException;
+import java.io.Reader;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import net.java.html.js.JavaScriptBody;
+import net.java.html.js.JavaScriptResource;
+import org.netbeans.html.boot.spi.Fn;
+import static org.testng.Assert.assertEquals;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+@JavaScriptResource("empty.js")
+public class CountFnCreationTest implements Fn.Presenter {
+    private int cnt;
+    
+    @JavaScriptBody(args = {}, body = "return;")
+    public static native void body();
+    
+    @Test public void countManyTimes() throws Exception {
+        class Res implements FindResources {
+            @Override
+            public void findResources(String path, Collection<? super URL> results, boolean oneIsEnough) {
+                try {
+                    ClassLoader l = CountFnCreationTest.class.getClassLoader();
+                    Enumeration<URL> en = l.getResources(path);
+                    while (en.hasMoreElements()) {
+                        results.add(en.nextElement());
+                    }
+                } catch (IOException ex) {
+                    throw new IllegalStateException(ex);
+                }
+            }
+        }
+        ClassLoader l = FnUtils.newLoader(new Res(), this, CountFnCreationTest.class.getClassLoader().getParent());
+        Method m = l.loadClass(CountFnCreationTest.class.getName()).getMethod("body");
+        Closeable c = Fn.activate(this);
+        try {
+            assertEquals(cnt, 0, "No functions yet");
+            m.invoke(null);
+            assertEquals(cnt, 1, "One function defined");
+            m.invoke(null);
+            assertEquals(cnt, 1, "Still one function");
+        } finally {
+            c.close();
+        }
+    }
+
+    @Override
+    public Fn defineFn(String code, String... names) {
+        cnt++;
+        return new MyFn(this);
+    }
+
+    @Override
+    public void displayPage(URL page, Runnable onPageLoad) {
+    }
+
+    @Override
+    public void loadScript(Reader code) throws Exception {
+    }
+    
+    private static final class MyFn extends Fn {
+
+        public MyFn(Presenter presenter) {
+            super(presenter);
+        }
+
+        @Override
+        public java.lang.Object invoke(java.lang.Object thiz, java.lang.Object... args) throws Exception {
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/test/java/org/netbeans/html/boot/impl/FnTest.java
----------------------------------------------------------------------
diff --git a/boot/src/test/java/org/netbeans/html/boot/impl/FnTest.java b/boot/src/test/java/org/netbeans/html/boot/impl/FnTest.java
new file mode 100644
index 0000000..d7c55ab
--- /dev/null
+++ b/boot/src/test/java/org/netbeans/html/boot/impl/FnTest.java
@@ -0,0 +1,186 @@
+/**
+ * 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.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+import java.io.Reader;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import javax.script.Invocable;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+import org.netbeans.html.boot.spi.Fn;
+import static org.testng.Assert.assertEquals;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public class FnTest extends JsClassLoaderBase {
+    private static Fn.Presenter presenter;
+    
+    public FnTest() {
+    }
+
+    @BeforeClass
+    public static void createClassLoader() throws Exception {
+        ScriptEngineManager sem = new ScriptEngineManager();
+        final ScriptEngine eng = sem.getEngineByMimeType("text/javascript");
+        
+        final URL my = FnTest.class.getProtectionDomain().getCodeSource().getLocation();
+        ClassLoader parent = JsClassLoaderTest.class.getClassLoader().getParent();
+        final URLClassLoader ul = new URLClassLoader(new URL[] { my }, parent);
+        
+        class Impl implements FindResources, Fn.Presenter {
+            @Override
+            public void findResources(String path, Collection<? super URL> results, boolean oneIsEnough) {
+                URL u = ul.findResource(path);
+                if (u != null) {
+                    results.add(u);
+                }
+            }
+
+            @Override
+            public Fn defineFn(String code, String... names) {
+                StringBuilder sb = new StringBuilder();
+                sb.append("(function() {");
+                sb.append("return function(");
+                String sep = "";
+                for (String n : names) {
+                    sb.append(sep);
+                    sb.append(n);
+                    sep = ", ";
+                }
+                sb.append(") {");
+                sb.append(code);
+                sb.append("};");
+                sb.append("})()");
+                try {
+                    final java.lang.Object val = eng.eval(sb.toString());
+                    return new Fn(this) {
+                        @Override
+                        public java.lang.Object invoke(java.lang.Object thiz, java.lang.Object... args) throws Exception {
+                            List<java.lang.Object> all = new ArrayList<java.lang.Object>(args.length + 1);
+                            all.add(thiz == null ? val : thiz);
+                            all.addAll(Arrays.asList(args));
+                            Invocable inv = (Invocable)eng;
+                            try {
+                                java.lang.Object ret = inv.invokeMethod(val, "call", all.toArray());
+                                return val.equals(ret) ? null : ret;
+                            } catch (ScriptException ex) {
+                                throw ex;
+                            }
+                        }
+                    };
+                } catch (ScriptException ex) {
+                    throw new LinkageError("Can't parse: " + sb, ex);
+                }
+            }
+
+            @Override
+            public void displayPage(URL resource, Runnable r) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void loadScript(Reader code) throws Exception {
+                eng.eval(code);
+            }
+        }
+        Impl impl = new Impl();
+        ClassLoader loader = FnUtils.newLoader(impl, impl, parent);
+        presenter = impl;
+        
+        Closeable close = FnContext.activate(impl);
+        methodClass = loader.loadClass(JsMethods.class.getName());
+        close.close();
+    }
+    
+    @Test public void flushingPresenter() throws IOException {
+        class FP implements Fn.Presenter, Flushable {
+            int flush;
+
+            @Override
+            public Fn defineFn(String code, String... names) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public void displayPage(URL page, Runnable onPageLoad) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public void loadScript(Reader code) throws Exception {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public void flush() throws IOException {
+                flush++;
+            }
+        }
+        
+        FP p = new FP();
+        Closeable c1 = Fn.activate(p);
+        Closeable c2 = Fn.activate(p);
+        c2.close();
+        assertEquals(p.flush, 0, "No flush yet");
+        c1.close();
+        assertEquals(p.flush, 1, "Now flushed");
+    }
+
+    @BeforeMethod public void initPresenter() {
+        FnContext.currentPresenter(presenter);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/test/java/org/netbeans/html/boot/impl/JavaScriptProcesorTest.java
----------------------------------------------------------------------
diff --git a/boot/src/test/java/org/netbeans/html/boot/impl/JavaScriptProcesorTest.java b/boot/src/test/java/org/netbeans/html/boot/impl/JavaScriptProcesorTest.java
new file mode 100644
index 0000000..1ef07b5
--- /dev/null
+++ b/boot/src/test/java/org/netbeans/html/boot/impl/JavaScriptProcesorTest.java
@@ -0,0 +1,167 @@
+/**
+ * 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.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Locale;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public class JavaScriptProcesorTest {
+    
+    @Test public void detectCallbackToNonExistingClass() throws IOException {
+        String code = "package x.y.z;\n"
+            + "import net.java.html.js.JavaScriptBody;\n"
+            + "class X {\n"
+            + "  @JavaScriptBody(args={\"r\"}, javacall=true, body =\n"
+            + "    \"r.@java.lang.Runable::run()();\"\n" // typo
+            + "  )\n"
+            + "  private static native void callback(Runnable r);\n"
+            + "}\n";
+        
+        Compile c = Compile.create("", code);
+        c.assertErrors();
+        c.assertError("java.lang.Runable"); // typo
+    }
+
+    @Test public void detectCallbackToNonExistingMethod() throws IOException {
+        String code = "package x.y.z;\n"
+            + "import net.java.html.js.JavaScriptBody;\n"
+            + "class X {\n"
+            + "  @JavaScriptBody(args={\"r\"}, javacall=true, body =\n"
+            + "    \"r.@java.lang.Runnable::cancel()();\"\n"
+            + "  )\n"
+            + "  private static native void callback(Runnable r);\n"
+            + "}\n";
+        
+        Compile c = Compile.create("", code);
+        c.assertErrors();
+        c.assertError("method cancel");
+    }
+
+    @Test public void detectCallbackToNonExistingParams() throws IOException {
+        String code = "package x.y.z;\n"
+            + "import net.java.html.js.JavaScriptBody;\n"
+            + "class X {\n"
+            + "  @JavaScriptBody(args={\"r\"}, javacall=true, body =\n"
+            + "    \"r.@java.lang.Runnable::run(I)(10);\"\n"
+            + "  )\n"
+            + "  private static native void callback(Runnable r);\n"
+            + "}\n";
+        
+        Compile c = Compile.create("", code);
+        c.assertErrors();
+        c.assertError("wrong parameters: (I)");
+    }
+
+    @Test public void objectTypeParamsAreOK() throws IOException {
+        String code = "package x.y.z;\n"
+            + "import net.java.html.js.JavaScriptBody;\n"
+            + "class X {\n"
+            + "  @JavaScriptBody(args={\"r\"}, javacall=true, body =\n"
+            + "    \"r.@java.lang.Object::equals(Ljava/lang/Object;)(null);\"\n"
+            + "  )\n"
+            + "  private static native void testEqual(Object r);\n"
+            + "}\n";
+        
+        Compile c = Compile.create("", code);
+        c.assertNoErrors();
+    }
+    
+    @Test public void misorderNotified() throws IOException {
+        String code = "package x.y.z;\n"
+            + "import net.java.html.js.JavaScriptBody;\n"
+            + "class X {\n"
+            + "  @JavaScriptBody(args={\"r\", \"a\", \"b\"}, body =\"\"\n"
+            + "  )\n"
+            + "  private static native void testEqual(Object p, String q, int r);\n"
+            + "}\n";
+        
+        Compile c = Compile.create("", code);
+        List<Diagnostic<? extends JavaFileObject>> warnings = c.getDiagnostics(Diagnostic.Kind.WARNING);
+        assertTrue(warnings.size() >= 1, "There are warnings: " + warnings);
+        for (Diagnostic<? extends JavaFileObject> w : warnings) {
+            if (w.getMessage(Locale.US).contains("Actual method parameter names and args")) {
+                return;
+            }
+        }
+        fail("Expecting order warning: " + warnings);
+    }
+
+    @Test public void needJavaScriptBodyToUseResource() throws IOException {
+        String code = "package x.y.z;\n"
+            + "import net.java.html.js.JavaScriptResource;\n"
+            + "@JavaScriptResource(\"x.html\")\n"
+            + "class X {\n"
+            + "  private static native void callback(Runnable r);\n"
+            + "}\n";
+        
+        Compile c = Compile.create("", code);
+        c.assertErrors();
+        c.assertError("needs @JavaScriptBody");
+    }
+    
+    @Test public void generatesCallbacksThatReturnObject() throws Exception {
+        Class<?> callbacksForTestPkg = Class.forName("org.netbeans.html.boot.impl.$JsCallbacks$");
+        Method m = callbacksForTestPkg.getDeclaredMethod("java_lang_Runnable$run$", Runnable.class);
+        assertEquals(m.getReturnType(), java.lang.Object.class, "All methods always return object");
+    }
+    
+    @Test public void hasInstanceField() throws Exception {
+        Class<?> callbacksForTestPkg = Class.forName("org.netbeans.html.boot.impl.$JsCallbacks$");
+        Field f = callbacksForTestPkg.getDeclaredField("VM");
+        f.setAccessible(true);
+        assertTrue(callbacksForTestPkg.isInstance(f.get(null)), "Singleton field VM");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/test/java/org/netbeans/html/boot/impl/JsCallbackTest.java
----------------------------------------------------------------------
diff --git a/boot/src/test/java/org/netbeans/html/boot/impl/JsCallbackTest.java b/boot/src/test/java/org/netbeans/html/boot/impl/JsCallbackTest.java
new file mode 100644
index 0000000..5b68349
--- /dev/null
+++ b/boot/src/test/java/org/netbeans/html/boot/impl/JsCallbackTest.java
@@ -0,0 +1,86 @@
+/**
+ * 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 static org.testng.Assert.*;
+import org.testng.annotations.Test;
+
+/** Verify behavior of the callback parser.
+ *
+ * @author Jaroslav Tulach
+ */
+public class JsCallbackTest {
+    
+    public JsCallbackTest() {
+    }
+    @Test public void missingTypeSpecification() {
+        String body = "console[attr] = function(msg) {\n"
+        + "  @org.netbeans.html.charts.Main::log(msg);\n"
+        + "};\n";
+        JsCallback instance = new JsCallbackImpl();
+        try {
+            String result = instance.parse(body);
+            fail("The parsing should fail!");
+        } catch (IllegalStateException ex) {
+            // OK
+        }
+    }
+
+
+    public class JsCallbackImpl extends JsCallback {
+        private String ident;
+        private String fqn;
+        private String method;
+        private String params;
+        
+        @Override
+        public CharSequence callMethod(String ident, String fqn, String method, String params) {
+            this.ident = ident;
+            this.fqn = fqn;
+            this.method = method;
+            this.params = params;
+            return "call";
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/test/java/org/netbeans/html/boot/impl/JsClassLoaderBase.java
----------------------------------------------------------------------
diff --git a/boot/src/test/java/org/netbeans/html/boot/impl/JsClassLoaderBase.java b/boot/src/test/java/org/netbeans/html/boot/impl/JsClassLoaderBase.java
new file mode 100644
index 0000000..9b458d2
--- /dev/null
+++ b/boot/src/test/java/org/netbeans/html/boot/impl/JsClassLoaderBase.java
@@ -0,0 +1,285 @@
+/**
+ * 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.Closeable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+import org.netbeans.html.boot.spi.Fn;
+import org.testng.Assert;
+import static org.testng.Assert.*;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public class JsClassLoaderBase {
+    protected static Class<?> methodClass;
+    
+    public JsClassLoaderBase() {
+    }
+    
+    @BeforeMethod
+    public void assertClassDefined() {
+        assertNotNull(methodClass, "BeforeClass set up code should provide methodClass");
+    }
+
+    @Test public void noParamMethod() throws Throwable {
+        Method plus = methodClass.getMethod("fortyTwo");
+        try {
+            final java.lang.Object val = plus.invoke(null);
+            assertTrue(val instanceof Number, "A number returned " + val);
+            assertEquals(((Number)val).intValue(), 42);
+        } catch (InvocationTargetException ex) {
+            throw ex.getTargetException();
+        }
+    }
+    
+    @Test public void testExecuteScript() throws Throwable {
+        Method plus = methodClass.getMethod("plus", int.class, int.class);
+        try {
+            assertEquals(plus.invoke(null, 10, 20), 30);
+        } catch (InvocationTargetException ex) {
+            throw ex.getTargetException();
+        }
+    }
+
+    @Test public void overloadedMethod() throws Throwable {
+        Method plus = methodClass.getMethod("plus", int.class);
+        try {
+            assertEquals(plus.invoke(null, 10), 10);
+        } catch (InvocationTargetException ex) {
+            throw ex.getTargetException();
+        }
+    }
+    
+    @Test public void instanceMethod() throws Throwable {
+        Method plus = methodClass.getMethod("plusInst", int.class);
+        java.lang.Object inst = methodClass.newInstance();
+        try {
+            assertEquals(plus.invoke(inst, 10), 10);
+        } catch (InvocationTargetException ex) {
+            throw ex.getTargetException();
+        }
+    }
+    
+    @Test public void staticThis() throws Throwable {
+        Method st = methodClass.getMethod("staticThis");
+        try {
+            assertNull(st.invoke(null));
+        } catch (InvocationTargetException ex) {
+            throw ex.getTargetException();
+        }
+    }
+
+    @Test public void getThis() throws Throwable {
+        java.lang.Object th = methodClass.newInstance();
+        Method st = methodClass.getMethod("getThis");
+        try {
+            assertEquals(st.invoke(th), th);
+        } catch (InvocationTargetException ex) {
+            throw ex.getTargetException();
+        }
+    }
+
+    @Test public void primitiveArrayReturn() throws Throwable {
+        Method st = methodClass.getMethod("both", double.class, double.class);
+        Throwable ex;
+        try {
+            java.lang.Object arr = st.invoke(null, 2, 5);
+            ex = null;
+        } catch (InvocationTargetException invoke) {
+            ex = invoke.getTargetException();
+        }
+        assertTrue(ex instanceof ClassCastException, "Primitive arrays aren't returned from JavaScript: " + ex);
+    }
+    
+    @Test public void truth() throws Throwable {
+        Method st = methodClass.getMethod("truth");
+        assertTrue((st.getModifiers() & Modifier.STATIC) != 0, "Is static");
+        assertEquals(st.invoke(null), Boolean.TRUE, "Can return boolean");
+    }
+    
+    @Test public void callback() throws Throwable {
+        class R implements Runnable {
+            int cnt;
+            
+            @Override
+            public void run() {
+                cnt++;
+            }
+        }
+        R r = new R();
+        
+        Method inc = methodClass.getMethod("callback", Runnable.class);
+        inc.invoke(null, r);
+        
+        assertEquals(r.cnt, 1, "Callback happened");
+    }
+    
+    @Test public void sumArray() throws Throwable {
+        Method st = methodClass.getMethod("sumArr", int[].class);
+        assertEquals(st.invoke(null, new int[] { 1, 2, 3 }), 6, "1+2+3 is six");
+    }
+    
+    @Test public void javaScriptResource() throws Throwable {
+        try {
+            Method st = methodClass.getMethod("useExternalMul", int.class, int.class);
+            assertEquals(st.invoke(null, 6, 7), 42, "Meaning of JavaScript?");
+        } catch (InvocationTargetException ex) {
+            throw ex.getTargetException();
+        }
+    }
+    
+    @Test public void callJavaScriptMethodOnOwnClass() throws Throwable {
+        try {
+            java.lang.Object thiz = methodClass.newInstance();
+            Method st = methodClass.getMethod("returnYourSelf", methodClass);
+            assertEquals(st.invoke(null, thiz), thiz, "Returns this");
+        } catch (InvocationTargetException ex) {
+            throw ex.getTargetException();
+        }
+    }
+    
+    @Test public void callStaticJavaMethod() throws Throwable {
+        Method st = methodClass.getMethod("staticCallback", int.class, int.class);
+        assertEquals(st.invoke(null, 6, 7), 42, "Meaning of JavaScript?");
+    }
+
+    @Test public void callStaticStringParamMethod() throws Throwable {
+        Method st = methodClass.getMethod("parseInt", String.class);
+        assertEquals(st.invoke(null, "42"), 42, "Meaning of JavaScript?");
+    }
+
+    @Test public void passEnum() throws Throwable {
+        Class<?> enmClazz = methodClass.getDeclaredClasses()[0];
+        assertTrue(Enum.class.isAssignableFrom(enmClazz), "It is an enum: " + enmClazz);
+        Class<? extends Enum> enmClazz2 = enmClazz.asSubclass(Enum.class);
+        Method st = methodClass.getMethod("fromEnum", enmClazz);
+        
+        java.lang.Object valueB = Enum.valueOf(enmClazz2, "B");
+        assertEquals(st.invoke(null, valueB), "B", "Converts to string");
+    }
+    
+    @Test public void firstLong() throws Throwable {
+        Method st = methodClass.getMethod("chooseLong", boolean.class, boolean.class, long.class, long.class);
+        assertEquals(st.invoke(null, true, false, 10, 20), 10L, "Take first value");
+    }
+
+    @Test public void secondLong() throws Throwable {
+        Method st = methodClass.getMethod("chooseLong", boolean.class, boolean.class, long.class, long.class);
+        assertEquals(st.invoke(null, false, true, 10, 20), 20L, "Take 2nd value");
+    }
+
+    @Test public void bothLong() throws Throwable {
+        Method st = methodClass.getMethod("chooseLong", boolean.class, boolean.class, long.class, long.class);
+        assertEquals(st.invoke(null, true, true, 10, 20), 30L, "Take both values");
+    }
+    
+    @Test public void recordError() throws Throwable {
+        Method st = methodClass.getMethod("recordError", java.lang.Object.class);
+        assertEquals(st.invoke(methodClass.newInstance(), "Hello"), "Hello", "The same parameter returned");
+    }
+    
+    @Test public void plusOrMul() throws Throwable {
+        Method st = methodClass.getMethod("plusOrMul", int.class, int.class);
+        assertNotNull(Fn.activePresenter(), "Is there a presenter?");
+        Closeable c = Fn.activate(null);
+        try {
+            assertNull(Fn.activePresenter(), "No presenter now");
+            assertEquals(st.invoke(null, 6, 7), 42, "Mul in Java");
+        } finally {
+            c.close();
+        }
+        assertNotNull(Fn.activePresenter(), "Is there a presenter again");
+        assertEquals(st.invoke(null, 6, 7), 13, "Plus in JavaScript");
+        c = Fn.activate(null);
+        try {
+            assertNull(Fn.activePresenter(), "No presenter again");
+            assertEquals(st.invoke(null, 6, 7), 42, "Mul in Java");
+        } finally {
+            c.close();
+        }
+        assertNotNull(Fn.activePresenter(), "Is there a presenter again");
+        assertEquals(st.invoke(null, 6, 7), 13, "Plus in JavaScript again");
+    }
+    
+    @Test public void arrayInOut() throws Throwable {
+        String[] arr = { "Ahoj" };
+        Method st = methodClass.getMethod("arr", java.lang.Object[].class);
+        java.lang.Object ret = st.invoke(null, (java.lang.Object) arr);
+        assertTrue(ret instanceof java.lang.Object[], "Expecting array: " + ret);
+        java.lang.Object[] res = (java.lang.Object[]) ret;
+        assertEquals(res.length, 1, "One element");
+        assertEquals(res[0], "Ahoj", "The right string");
+    }
+
+    @Test public void parametricCallback() throws Throwable {
+        Map<String,Number> map = new HashMap<String, Number>();
+        Method st = methodClass.getMethod("callParamTypes", Map.class, int.class);
+        st.invoke(null, map, 42);
+        assertEquals(map.get("key").intValue(), 42, "The right value");
+    }
+    
+   @Test public void checkTheTypeOfThrownException() throws Throwable {
+        FnContext.currentPresenter(null);
+        assertNull(Fn.activePresenter(), "No presenter is activer right now");
+        java.lang.Object res = null;
+        try {
+            Method st = methodClass.getMethod("plus", int.class, int.class);
+            try {
+                res = st.invoke(null, 40, 2);
+                Assert.fail("Native method should throw IllegalStateException. Was: " + res);
+            } catch (InvocationTargetException ex) {
+                throw ex.getTargetException();
+            }
+        } catch (IllegalStateException ex) {
+            assertEquals(ex.getMessage(), "No presenter active. Use BrwsrCtx.execute!");
+        }
+    }    
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/boot/src/test/java/org/netbeans/html/boot/impl/JsClassLoaderTest.java
----------------------------------------------------------------------
diff --git a/boot/src/test/java/org/netbeans/html/boot/impl/JsClassLoaderTest.java b/boot/src/test/java/org/netbeans/html/boot/impl/JsClassLoaderTest.java
new file mode 100644
index 0000000..d69a761
--- /dev/null
+++ b/boot/src/test/java/org/netbeans/html/boot/impl/JsClassLoaderTest.java
@@ -0,0 +1,161 @@
+/**
+ * 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.Closeable;
+import java.io.Reader;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import javax.script.Invocable;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+import org.netbeans.html.boot.spi.Fn;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public class JsClassLoaderTest extends JsClassLoaderBase{
+    private static Fn.Presenter loader;
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        ScriptEngineManager sem = new ScriptEngineManager();
+        final ScriptEngine eng = sem.getEngineByMimeType("text/javascript");
+        
+        final URL my = JsClassLoaderTest.class.getProtectionDomain().getCodeSource().getLocation();
+        ClassLoader parent = JsClassLoaderTest.class.getClassLoader().getParent();
+        final URLClassLoader ul = new URLClassLoader(new URL[] { my }, parent);
+        class MyCL extends FnUtils.JsClassLoaderImpl implements Fn.Presenter {
+
+            public MyCL(ClassLoader parent) {
+                super(parent, null, null);
+            }
+            
+            @Override
+            protected URL findResource(String name) {
+                return ul.getResource(name);
+            }
+            @Override
+            public Fn defineFn(String code, String... names) {
+                StringBuilder sb = new StringBuilder();
+                sb.append("(function() {");
+                sb.append("return function(");
+                String sep = "";
+                for (String n : names) {
+                    sb.append(sep);
+                    sb.append(n);
+                    sep = ", ";
+                }
+                sb.append(") {");
+                sb.append(code);
+                sb.append("};");
+                sb.append("})()");
+                try {
+                    final java.lang.Object val = eng.eval(sb.toString());
+                    return new Fn(this) {
+                        @Override
+                        public java.lang.Object invoke(java.lang.Object thiz, java.lang.Object... args) throws Exception {
+                            List<java.lang.Object> all = new ArrayList<java.lang.Object>(args.length + 1);
+                            all.add(thiz == null ? val : thiz);
+                            all.addAll(Arrays.asList(args));
+                            Invocable inv = (Invocable)eng;
+                            try {
+                                java.lang.Object ret = inv.invokeMethod(val, "call", all.toArray());
+                                return val.equals(ret) ? null : ret;
+                            } catch (Exception ex) {
+                                throw ex;
+                            }
+                        }
+                    };
+                } catch (ScriptException ex) {
+                    throw new LinkageError("Can't parse: " + sb, ex);
+                }
+            }
+
+            @Override
+            protected Enumeration<URL> findResources(String name) {
+                URL u = findResource(name);
+                List<URL> arr = new ArrayList<URL>();
+                if (u != null) {
+                    arr.add(u);
+                }
+                return Collections.enumeration(arr);
+            }
+
+            @Override
+            public void loadScript(Reader code) throws ScriptException {
+                eng.eval(code);
+            }
+
+            @Override
+            public void displayPage(URL page, Runnable onPageLoad) {
+                throw new UnsupportedOperationException();
+            }
+        };
+        
+        MyCL l = new MyCL(parent);
+        Closeable close = FnContext.activate(l);
+        methodClass = l.loadClass(JsMethods.class.getName());
+        close.close();
+        loader = l;
+    }
+    
+    @BeforeMethod public void initPresenter() {
+        FnContext.currentPresenter(loader);
+    }
+
+    @AfterClass
+    public static void cleanUp() {
+        methodClass = null;
+    }
+}
\ No newline at end of file