Better (more extensible and documented) API for the Launcher emul
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Thu, 31 Jan 2013 16:58:27 +0100
branchemul
changeset 622a07253cf2ca4
parent 621 6daa39b17563
child 623 4af0d3dedb9d
Better (more extensible and documented) API for the Launcher
launcher/pom.xml
launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java
launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Console.java
launcher/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java
launcher/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java
launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java
launcher/src/main/java/org/apidesign/bck2brwsr/launcher/MethodInvocation.java
launcher/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java
launcher/src/main/resources/org/apidesign/bck2brwsr/launcher/harness.xhtml
vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java
     1.1 --- a/launcher/pom.xml	Thu Jan 31 16:13:04 2013 +0100
     1.2 +++ b/launcher/pom.xml	Thu Jan 31 16:58:27 2013 +0100
     1.3 @@ -23,6 +23,14 @@
     1.4                      <target>1.7</target>
     1.5                  </configuration>
     1.6              </plugin>
     1.7 +            <plugin>
     1.8 +                <groupId>org.apache.maven.plugins</groupId>
     1.9 +                <artifactId>maven-javadoc-plugin</artifactId>
    1.10 +                <version>2.8.1</version>
    1.11 +                <configuration>
    1.12 +                    <excludePackageNames>org.apidesign.bck2brwsr.launcher.impl</excludePackageNames>
    1.13 +                </configuration>
    1.14 +            </plugin>
    1.15          </plugins>
    1.16      </build>
    1.17      <properties>
     2.1 --- a/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java	Thu Jan 31 16:13:04 2013 +0100
     2.2 +++ b/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java	Thu Jan 31 16:58:27 2013 +0100
     2.3 @@ -55,9 +55,9 @@
     2.4   */
     2.5  final class Bck2BrwsrLauncher extends Launcher implements Closeable {
     2.6      private static final Logger LOG = Logger.getLogger(Bck2BrwsrLauncher.class.getName());
     2.7 -    private static final MethodInvocation END = new MethodInvocation(null, null, null);
     2.8 -    private Set<ClassLoader> loaders = new LinkedHashSet<>();
     2.9 -    private BlockingQueue<MethodInvocation> methods = new LinkedBlockingQueue<>();
    2.10 +    private static final InvocationContext END = new InvocationContext(null, null, null);
    2.11 +    private final Set<ClassLoader> loaders = new LinkedHashSet<>();
    2.12 +    private final BlockingQueue<InvocationContext> methods = new LinkedBlockingQueue<>();
    2.13      private long timeOut;
    2.14      private final Res resources = new Res();
    2.15      private final String cmd;
    2.16 @@ -70,9 +70,8 @@
    2.17      }
    2.18      
    2.19      @Override
    2.20 -     MethodInvocation addMethod(Class<?> clazz, String method, String html) throws IOException {
    2.21 -        loaders.add(clazz.getClassLoader());
    2.22 -        MethodInvocation c = new MethodInvocation(clazz.getName(), method, html);
    2.23 +    InvocationContext runMethod(InvocationContext c) throws IOException {
    2.24 +        loaders.add(c.clazz.getClassLoader());
    2.25          methods.add(c);
    2.26          try {
    2.27              c.await(timeOut);
    2.28 @@ -154,7 +153,7 @@
    2.29          ), "/execute");
    2.30          conf.addHttpHandler(new HttpHandler() {
    2.31              int cnt;
    2.32 -            List<MethodInvocation> cases = new ArrayList<>();
    2.33 +            List<InvocationContext> cases = new ArrayList<>();
    2.34              @Override
    2.35              public void service(Request request, Response response) throws Exception {
    2.36                  String id = request.getParameter("request");
    2.37 @@ -166,7 +165,7 @@
    2.38                      cases.get(Integer.parseInt(id)).result(value, null);
    2.39                  }
    2.40                  
    2.41 -                MethodInvocation mi = methods.take();
    2.42 +                InvocationContext mi = methods.take();
    2.43                  if (mi == END) {
    2.44                      response.getWriter().write("");
    2.45                      wait.countDown();
    2.46 @@ -176,7 +175,7 @@
    2.47                  }
    2.48                  
    2.49                  cases.add(mi);
    2.50 -                final String cn = mi.className;
    2.51 +                final String cn = mi.clazz.getName();
    2.52                  final String mn = mi.methodName;
    2.53                  LOG.log(Level.INFO, "Request for {0} case. Sending {1}.{2}", new Object[]{cnt, cn, mn});
    2.54                  response.getWriter().write("{"
     3.1 --- a/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Console.java	Thu Jan 31 16:13:04 2013 +0100
     3.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3 @@ -1,253 +0,0 @@
     3.4 -/**
     3.5 - * Back 2 Browser Bytecode Translator
     3.6 - * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     3.7 - *
     3.8 - * This program is free software: you can redistribute it and/or modify
     3.9 - * it under the terms of the GNU General Public License as published by
    3.10 - * the Free Software Foundation, version 2 of the License.
    3.11 - *
    3.12 - * This program is distributed in the hope that it will be useful,
    3.13 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.14 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.15 - * GNU General Public License for more details.
    3.16 - *
    3.17 - * You should have received a copy of the GNU General Public License
    3.18 - * along with this program. Look for COPYING file in the top folder.
    3.19 - * If not, see http://opensource.org/licenses/GPL-2.0.
    3.20 - */
    3.21 -package org.apidesign.bck2brwsr.launcher;
    3.22 -
    3.23 -import java.io.IOException;
    3.24 -import java.io.InputStream;
    3.25 -import java.lang.reflect.InvocationTargetException;
    3.26 -import java.lang.reflect.Method;
    3.27 -import java.lang.reflect.Modifier;
    3.28 -import java.net.URL;
    3.29 -import java.util.Enumeration;
    3.30 -import org.apidesign.bck2brwsr.core.JavaScriptBody;
    3.31 -
    3.32 -/**
    3.33 - *
    3.34 - * @author Jaroslav Tulach <jtulach@netbeans.org>
    3.35 - */
    3.36 -public class Console {
    3.37 -    static {
    3.38 -        turnAssetionStatusOn();
    3.39 -    }
    3.40 -    
    3.41 -    @JavaScriptBody(args = {"id", "attr"}, body = 
    3.42 -        "return window.document.getElementById(id)[attr].toString();")
    3.43 -    private static native Object getAttr(String id, String attr);
    3.44 -
    3.45 -    @JavaScriptBody(args = {"id", "attr", "value"}, body = 
    3.46 -        "window.document.getElementById(id)[attr] = value;")
    3.47 -    private static native void setAttr(String id, String attr, Object value);
    3.48 -    
    3.49 -    @JavaScriptBody(args = {}, body = "return; window.close();")
    3.50 -    private static native void closeWindow();
    3.51 -
    3.52 -    private static void log(String newText) {
    3.53 -        String id = "bck2brwsr.result";
    3.54 -        String attr = "value";
    3.55 -        setAttr(id, attr, getAttr(id, attr) + "\n" + newText);
    3.56 -        setAttr(id, "scrollTop", getAttr(id, "scrollHeight"));
    3.57 -    }
    3.58 -    
    3.59 -    public static void execute() throws Exception {
    3.60 -        String clazz = (String) getAttr("clazz", "value");
    3.61 -        String method = (String) getAttr("method", "value");
    3.62 -        Object res = invokeMethod(clazz, method);
    3.63 -        setAttr("bck2brwsr.result", "value", res);
    3.64 -    }
    3.65 -
    3.66 -    @JavaScriptBody(args = { "url", "callback", "arr" }, body = ""
    3.67 -        + "var request = new XMLHttpRequest();\n"
    3.68 -        + "request.open('GET', url, true);\n"
    3.69 -        + "request.onreadystatechange = function() {\n"
    3.70 -        + "  if (this.readyState!==4) return;\n"
    3.71 -        + "  arr[0] = this.responseText;\n"
    3.72 -        + "  callback.run__V();\n"
    3.73 -        + "};"
    3.74 -        + "request.send();"
    3.75 -    )
    3.76 -    private static native void loadText(String url, Runnable callback, String[] arr) throws IOException;
    3.77 -    
    3.78 -    public static void harness(String url) throws IOException {
    3.79 -        log("Connecting to " + url);
    3.80 -        Request r = new Request(url);
    3.81 -    }
    3.82 -    
    3.83 -    private static class Request implements Runnable {
    3.84 -        private final String[] arr = { null };
    3.85 -        private final String url;
    3.86 -
    3.87 -        private Request(String url) throws IOException {
    3.88 -            this.url = url;
    3.89 -            loadText(url, this, arr);
    3.90 -        }
    3.91 -        
    3.92 -        @Override
    3.93 -        public void run() {
    3.94 -            try {
    3.95 -                String data = arr[0];
    3.96 -                log("\nGot \"" + data + "\"");
    3.97 -                
    3.98 -                if (data == null) {
    3.99 -                    log("Some error exiting");
   3.100 -                    closeWindow();
   3.101 -                    return;
   3.102 -                }
   3.103 -                
   3.104 -                if (data.isEmpty()) {
   3.105 -                    log("No data, exiting");
   3.106 -                    closeWindow();
   3.107 -                    return;
   3.108 -                }
   3.109 -                
   3.110 -                Case c = Case.parseData(data);
   3.111 -                if (c.getHtmlFragment() != null) {
   3.112 -                    setAttr("bck2brwsr.fragment", "innerHTML", c.getHtmlFragment());
   3.113 -                }
   3.114 -                log("Invoking " + c.getClassName() + '.' + c.getMethodName() + " as request: " + c.getRequestId());
   3.115 -
   3.116 -                Object result = invokeMethod(c.getClassName(), c.getMethodName());
   3.117 -                
   3.118 -                setAttr("bck2brwsr.fragment", "innerHTML", "");
   3.119 -                log("Result: " + result);
   3.120 -                
   3.121 -                result = encodeURL("" + result);
   3.122 -                
   3.123 -                log("Sending back: " + url + "?request=" + c.getRequestId() + "&result=" + result);
   3.124 -                String u = url + "?request=" + c.getRequestId() + "&result=" + result;
   3.125 -                
   3.126 -                loadText(u, this, arr);
   3.127 -                
   3.128 -            } catch (Exception ex) {
   3.129 -                log(ex.getMessage());
   3.130 -            }
   3.131 -        }
   3.132 -    }
   3.133 -    
   3.134 -    private static String encodeURL(String r) {
   3.135 -        StringBuilder sb = new StringBuilder();
   3.136 -        for (int i = 0; i < r.length(); i++) {
   3.137 -            int ch = r.charAt(i);
   3.138 -            if (ch < 32 || ch == '%' || ch == '+') {
   3.139 -                sb.append("%").append(("0" + Integer.toHexString(ch)).substring(0, 2));
   3.140 -            } else {
   3.141 -                if (ch == 32) {
   3.142 -                    sb.append("+");
   3.143 -                } else {
   3.144 -                    sb.append((char)ch);
   3.145 -                }
   3.146 -            }
   3.147 -        }
   3.148 -        return sb.toString();
   3.149 -    }
   3.150 -    
   3.151 -    static String invoke(String clazz, String method) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException {
   3.152 -        final Object r = invokeMethod(clazz, method);
   3.153 -        return r == null ? "null" : r.toString().toString();
   3.154 -    }
   3.155 -
   3.156 -    /** Helper method that inspects the classpath and loads given resource
   3.157 -     * (usually a class file). Used while running tests in Rhino.
   3.158 -     * 
   3.159 -     * @param name resource name to find
   3.160 -     * @return the array of bytes in the given resource
   3.161 -     * @throws IOException I/O in case something goes wrong
   3.162 -     */
   3.163 -    public static byte[] read(String name) throws IOException {
   3.164 -        URL u = null;
   3.165 -        Enumeration<URL> en = Console.class.getClassLoader().getResources(name);
   3.166 -        while (en.hasMoreElements()) {
   3.167 -            u = en.nextElement();
   3.168 -        }
   3.169 -        if (u == null) {
   3.170 -            throw new IOException("Can't find " + name);
   3.171 -        }
   3.172 -        try (InputStream is = u.openStream()) {
   3.173 -            byte[] arr;
   3.174 -            arr = new byte[is.available()];
   3.175 -            int offset = 0;
   3.176 -            while (offset < arr.length) {
   3.177 -                int len = is.read(arr, offset, arr.length - offset);
   3.178 -                if (len == -1) {
   3.179 -                    throw new IOException("Can't read " + name);
   3.180 -                }
   3.181 -                offset += len;
   3.182 -            }
   3.183 -            return arr;
   3.184 -        }
   3.185 -    }
   3.186 -   
   3.187 -    private static Object invokeMethod(String clazz, String method) 
   3.188 -    throws ClassNotFoundException, InvocationTargetException, 
   3.189 -    SecurityException, IllegalAccessException, IllegalArgumentException,
   3.190 -    InstantiationException {
   3.191 -        Method found = null;
   3.192 -        Class<?> c = Class.forName(clazz);
   3.193 -        for (Method m : c.getMethods()) {
   3.194 -            if (m.getName().equals(method)) {
   3.195 -                found = m;
   3.196 -            }
   3.197 -        }
   3.198 -        Object res;
   3.199 -        if (found != null) {
   3.200 -            try {
   3.201 -                if ((found.getModifiers() & Modifier.STATIC) != 0) {
   3.202 -                    res = found.invoke(null);
   3.203 -                } else {
   3.204 -                    res = found.invoke(c.newInstance());
   3.205 -                }
   3.206 -            } catch (Throwable ex) {
   3.207 -                res = ex.getClass().getName() + ":" + ex.getMessage();
   3.208 -            }
   3.209 -        } else {
   3.210 -            res = "Can't find method " + method + " in " + clazz;
   3.211 -        }
   3.212 -        return res;
   3.213 -    }
   3.214 -
   3.215 -    @JavaScriptBody(args = {}, body = "vm.desiredAssertionStatus = true;")
   3.216 -    private static void turnAssetionStatusOn() {
   3.217 -    }
   3.218 -    
   3.219 -    private static final class Case {
   3.220 -        private final Object data;
   3.221 -
   3.222 -        private Case(Object data) {
   3.223 -            this.data = data;
   3.224 -        }
   3.225 -        
   3.226 -        public static Case parseData(String s) {
   3.227 -            return new Case(toJSON(s));
   3.228 -        }
   3.229 -        
   3.230 -        public String getMethodName() {
   3.231 -            return value("methodName", data);
   3.232 -        }
   3.233 -
   3.234 -        public String getClassName() {
   3.235 -            return value("className", data);
   3.236 -        }
   3.237 -        
   3.238 -        public String getRequestId() {
   3.239 -            return value("request", data);
   3.240 -        }
   3.241 -
   3.242 -        public String getHtmlFragment() {
   3.243 -            return value("html", data);
   3.244 -        }
   3.245 -        
   3.246 -        @JavaScriptBody(args = "s", body = "return eval('(' + s + ')');")
   3.247 -        private static native Object toJSON(String s);
   3.248 -        
   3.249 -        @JavaScriptBody(args = {"p", "d"}, body = 
   3.250 -              "var v = d[p];\n"
   3.251 -            + "if (typeof v === 'undefined') return null;\n"
   3.252 -            + "return v.toString();"
   3.253 -        )
   3.254 -        private static native String value(String p, Object d);
   3.255 -    }
   3.256 -}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java	Thu Jan 31 16:58:27 2013 +0100
     4.3 @@ -0,0 +1,97 @@
     4.4 +/**
     4.5 + * Back 2 Browser Bytecode Translator
     4.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4.7 + *
     4.8 + * This program is free software: you can redistribute it and/or modify
     4.9 + * it under the terms of the GNU General Public License as published by
    4.10 + * the Free Software Foundation, version 2 of the License.
    4.11 + *
    4.12 + * This program is distributed in the hope that it will be useful,
    4.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    4.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    4.15 + * GNU General Public License for more details.
    4.16 + *
    4.17 + * You should have received a copy of the GNU General Public License
    4.18 + * along with this program. Look for COPYING file in the top folder.
    4.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    4.20 + */
    4.21 +package org.apidesign.bck2brwsr.launcher;
    4.22 +
    4.23 +import java.io.IOException;
    4.24 +import java.util.concurrent.CountDownLatch;
    4.25 +import java.util.concurrent.TimeUnit;
    4.26 +
    4.27 +/** Represents individual method invocation, its context and its result.
    4.28 + *
    4.29 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    4.30 + */
    4.31 +public final class InvocationContext {
    4.32 +    final CountDownLatch wait = new CountDownLatch(1);
    4.33 +    final Class<?> clazz;
    4.34 +    final String methodName;
    4.35 +    private final Launcher launcher;
    4.36 +    private String result;
    4.37 +    private Throwable exception;
    4.38 +    String html;
    4.39 +    String httpContent;
    4.40 +    String httpType;
    4.41 +    String httpPath;
    4.42 +
    4.43 +    InvocationContext(Launcher launcher, Class<?> clazz, String methodName) {
    4.44 +        this.launcher = launcher;
    4.45 +        this.clazz = clazz;
    4.46 +        this.methodName = methodName;
    4.47 +    }
    4.48 +    
    4.49 +    /** An HTML fragment to be available for the execution. Useful primarily when
    4.50 +     * executing in a browser via {@link Launcher#createBrowser(java.lang.String)}.
    4.51 +     * @param html the html fragment
    4.52 +     */
    4.53 +    public void setHtmlFragment(String html) {
    4.54 +        this.html = html;
    4.55 +    }
    4.56 +    
    4.57 +    /** HTTP resource to be available during execution. An invocation may
    4.58 +     * perform an HTTP query and obtain a resource relative to the page.
    4.59 +     */
    4.60 +    public void setHttpResource(String relativePath, String mimeType, String content) {
    4.61 +        this.httpPath = relativePath;
    4.62 +        this.httpType = mimeType;
    4.63 +        this.httpContent = content;
    4.64 +    }
    4.65 +    
    4.66 +    /** Invokes the associated method. 
    4.67 +     * @return the textual result of the invocation
    4.68 +     */
    4.69 +    public String invoke() throws IOException {
    4.70 +        launcher.runMethod(this);
    4.71 +        return toString();
    4.72 +    }
    4.73 +    
    4.74 +    /** Obtains textual result of the invocation.
    4.75 +     * @return text representing the exception or result value
    4.76 +     */
    4.77 +    @Override
    4.78 +    public String toString() {
    4.79 +        if (exception != null) {
    4.80 +            return exception.toString();
    4.81 +        }
    4.82 +        return result;
    4.83 +    }
    4.84 +    
    4.85 +    /**
    4.86 +     * @param timeOut
    4.87 +     * @throws InterruptedException 
    4.88 +     */
    4.89 +    void await(long timeOut) throws InterruptedException {
    4.90 +        wait.await(timeOut, TimeUnit.MILLISECONDS);
    4.91 +    }
    4.92 +    
    4.93 +    void result(String r, Throwable e) {
    4.94 +        this.result = r;
    4.95 +        this.exception = e;
    4.96 +        wait.countDown();
    4.97 +    }
    4.98 +
    4.99 +    
   4.100 +}
     5.1 --- a/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java	Thu Jan 31 16:13:04 2013 +0100
     5.2 +++ b/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java	Thu Jan 31 16:58:27 2013 +0100
     5.3 @@ -17,6 +17,7 @@
     5.4   */
     5.5  package org.apidesign.bck2brwsr.launcher;
     5.6  
     5.7 +import org.apidesign.bck2brwsr.launcher.impl.Console;
     5.8  import java.io.IOException;
     5.9  import java.io.InputStream;
    5.10  import java.net.URL;
    5.11 @@ -43,18 +44,17 @@
    5.12      private Object console;
    5.13      
    5.14      
    5.15 -    @Override MethodInvocation addMethod(Class<?> clazz, String method, String html) {
    5.16 -        loaders.add(clazz.getClassLoader());
    5.17 -        MethodInvocation mi = new MethodInvocation(clazz.getName(), method, html);
    5.18 +    @Override InvocationContext runMethod(InvocationContext mi) {
    5.19 +        loaders.add(mi.clazz.getClassLoader());
    5.20          try {
    5.21              long time = System.currentTimeMillis();
    5.22 -            LOG.log(Level.FINE, "Invoking {0}.{1}", new Object[]{mi.className, mi.methodName});
    5.23 +            LOG.log(Level.FINE, "Invoking {0}.{1}", new Object[]{mi.clazz.getName(), mi.methodName});
    5.24              String res = code.invokeMethod(
    5.25                  console,
    5.26                  "invoke__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2",
    5.27 -                mi.className, mi.methodName).toString();
    5.28 +                mi.clazz.getName(), mi.methodName).toString();
    5.29              time = System.currentTimeMillis() - time;
    5.30 -            LOG.log(Level.FINE, "Resut of {0}.{1} = {2} in {3} ms", new Object[]{mi.className, mi.methodName, res, time});
    5.31 +            LOG.log(Level.FINE, "Resut of {0}.{1} = {2} in {3} ms", new Object[]{mi.clazz.getName(), mi.methodName, res, time});
    5.32              mi.result(res, null);
    5.33          } catch (ScriptException | NoSuchMethodException ex) {
    5.34              mi.result(null, ex);
    5.35 @@ -89,7 +89,7 @@
    5.36          ScriptEngine mach = sem.getEngineByExtension("js");
    5.37  
    5.38          sb.append(
    5.39 -              "\nvar vm = new bck2brwsr(org.apidesign.bck2brwsr.launcher.Console.read);"
    5.40 +              "\nvar vm = new bck2brwsr(org.apidesign.bck2brwsr.launcher.impl.Console.read);"
    5.41              + "\nfunction initVM() { return vm; };"
    5.42              + "\n");
    5.43  
     6.1 --- a/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java	Thu Jan 31 16:13:04 2013 +0100
     6.2 +++ b/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java	Thu Jan 31 16:58:27 2013 +0100
     6.3 @@ -24,7 +24,8 @@
     6.4  import org.apidesign.vm4brwsr.Bck2Brwsr;
     6.5  
     6.6  /** An abstraction for executing tests in a Bck2Brwsr virtual machine.
     6.7 - * Either in JavaScript engine, or in external browser.
     6.8 + * Either in {@linkm Launcher#createJavaScript JavaScript engine}, 
     6.9 + * or in {@linkm Launcher#createBrowser external browser}.
    6.10   *
    6.11   * @author Jaroslav Tulach <jtulach@netbeans.org>
    6.12   */
    6.13 @@ -32,39 +33,83 @@
    6.14  
    6.15      Launcher() {
    6.16      }
    6.17 +
    6.18 +    /** Initializes the launcher. This may mean starting a web browser or
    6.19 +     * initializing execution engine.
    6.20 +     * @throws IOException if something goes wrong
    6.21 +     */
    6.22 +    public abstract void initialize() throws IOException;
    6.23      
    6.24 -    abstract MethodInvocation addMethod(Class<?> clazz, String method, String html) throws IOException; 
    6.25 -
    6.26 -    public abstract void initialize() throws IOException;
    6.27 +    /** Shuts down the launcher.
    6.28 +     * @throws IOException if something goes wrong
    6.29 +     */
    6.30      public abstract void shutdown() throws IOException;
    6.31 -    public MethodInvocation invokeMethod(Class<?> clazz, String method, String html) throws IOException {
    6.32 -        return addMethod(clazz, method, html);
    6.33 +    
    6.34 +    
    6.35 +    /** Builds an invocation context. The context can later be customized
    6.36 +     * and {@link InvocationContext#invoke() invoked}.
    6.37 +     * 
    6.38 +     * @param clazz the class to execute method from
    6.39 +     * @param method the method to execute
    6.40 +     * @return the context pointing to the selected method
    6.41 +     */
    6.42 +    public InvocationContext createInvocation(Class<?> clazz, String method) {
    6.43 +        return new InvocationContext(this, clazz, method);
    6.44      }
    6.45      
    6.46 -    
    6.47  
    6.48 +    /** Creates launcher that uses internal JavaScript engine (Rhino).
    6.49 +     * @return the launcher
    6.50 +     */
    6.51      public static Launcher createJavaScript() {
    6.52          final JSLauncher l = new JSLauncher();
    6.53          l.addClassLoader(Bck2Brwsr.class.getClassLoader());
    6.54          return l;
    6.55      }
    6.56      
    6.57 +    /** Creates launcher that is using external browser.
    6.58 +     * 
    6.59 +     * @param cmd <code>null</code> to use <code>java.awt.Desktop</code> to show the launcher
    6.60 +     *    or a string to execute in an external process (with a parameter to the URL)
    6.61 +     * @return launcher executing in external browser.
    6.62 +     */
    6.63      public static Launcher createBrowser(String cmd) {
    6.64          final Bck2BrwsrLauncher l = new Bck2BrwsrLauncher(cmd);
    6.65          l.addClassLoader(Bck2Brwsr.class.getClassLoader());
    6.66          l.setTimeout(180000);
    6.67          return l;
    6.68      }
    6.69 +    
    6.70 +    /** Starts an HTTP server which provides access to classes and resources
    6.71 +     * available in the <code>classes</code> URL and shows a start page
    6.72 +     * available as {@link ClassLoader#getResource(java.lang.String)} from the
    6.73 +     * provide classloader. Opens a browser with URL showing the start page.
    6.74 +     * 
    6.75 +     * @param classes classloader offering access to classes and resources
    6.76 +     * @param startpage page to show in the browser
    6.77 +     * @return interface that allows one to stop the server
    6.78 +     * @throws IOException if something goes wrong
    6.79 +     */
    6.80      public static Closeable showURL(URLClassLoader classes, String startpage) throws IOException {
    6.81          Bck2BrwsrLauncher l = new Bck2BrwsrLauncher(null);
    6.82          l.addClassLoader(classes);
    6.83          l.showURL(startpage);
    6.84          return l;
    6.85      }
    6.86 +    /** Starts an HTTP server which provides access to certain directory.
    6.87 +     * The <code>startpage</code> should be relative location inside the root 
    6.88 +     * driecotry
    6.89 +     * Opens a browser with URL showing the start page.
    6.90 +     * 
    6.91 +     * @param directory the root directory on disk
    6.92 +     * @praam startpage relative path from the root to the page
    6.93 +     * @exception IOException if something goes wrong.
    6.94 +     */
    6.95      public static Closeable showDir(File directory, String startpage) throws IOException {
    6.96          Bck2BrwsrLauncher l = new Bck2BrwsrLauncher(null);
    6.97          l.showDirectory(directory, startpage);
    6.98          return l;
    6.99      }
   6.100  
   6.101 +    abstract InvocationContext runMethod(InvocationContext c) throws IOException; 
   6.102  }
     7.1 --- a/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/MethodInvocation.java	Thu Jan 31 16:13:04 2013 +0100
     7.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.3 @@ -1,59 +0,0 @@
     7.4 -/**
     7.5 - * Back 2 Browser Bytecode Translator
     7.6 - * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     7.7 - *
     7.8 - * This program is free software: you can redistribute it and/or modify
     7.9 - * it under the terms of the GNU General Public License as published by
    7.10 - * the Free Software Foundation, version 2 of the License.
    7.11 - *
    7.12 - * This program is distributed in the hope that it will be useful,
    7.13 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
    7.14 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    7.15 - * GNU General Public License for more details.
    7.16 - *
    7.17 - * You should have received a copy of the GNU General Public License
    7.18 - * along with this program. Look for COPYING file in the top folder.
    7.19 - * If not, see http://opensource.org/licenses/GPL-2.0.
    7.20 - */
    7.21 -package org.apidesign.bck2brwsr.launcher;
    7.22 -
    7.23 -import java.util.concurrent.CountDownLatch;
    7.24 -import java.util.concurrent.TimeUnit;
    7.25 -
    7.26 -/**
    7.27 - *
    7.28 - * @author Jaroslav Tulach <jtulach@netbeans.org>
    7.29 - */
    7.30 -public final class MethodInvocation {
    7.31 -    final CountDownLatch wait = new CountDownLatch(1);
    7.32 -    final String className;
    7.33 -    final String methodName;
    7.34 -    final String html;
    7.35 -    private String result;
    7.36 -    private Throwable exception;
    7.37 -
    7.38 -    MethodInvocation(String className, String methodName, String html) {
    7.39 -        this.className = className;
    7.40 -        this.methodName = methodName;
    7.41 -        this.html = html;
    7.42 -    }
    7.43 -    
    7.44 -    void await(long timeOut) throws InterruptedException {
    7.45 -        wait.await(timeOut, TimeUnit.MILLISECONDS);
    7.46 -    }
    7.47 -    
    7.48 -    void result(String r, Throwable e) {
    7.49 -        this.result = r;
    7.50 -        this.exception = e;
    7.51 -        wait.countDown();
    7.52 -    }
    7.53 -
    7.54 -    @Override
    7.55 -    public String toString() {
    7.56 -        if (exception != null) {
    7.57 -            return exception.toString();
    7.58 -        }
    7.59 -        return result;
    7.60 -    }
    7.61 -    
    7.62 -}
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java	Thu Jan 31 16:58:27 2013 +0100
     8.3 @@ -0,0 +1,255 @@
     8.4 +/**
     8.5 + * Back 2 Browser Bytecode Translator
     8.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     8.7 + *
     8.8 + * This program is free software: you can redistribute it and/or modify
     8.9 + * it under the terms of the GNU General Public License as published by
    8.10 + * the Free Software Foundation, version 2 of the License.
    8.11 + *
    8.12 + * This program is distributed in the hope that it will be useful,
    8.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    8.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    8.15 + * GNU General Public License for more details.
    8.16 + *
    8.17 + * You should have received a copy of the GNU General Public License
    8.18 + * along with this program. Look for COPYING file in the top folder.
    8.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    8.20 + */
    8.21 +package org.apidesign.bck2brwsr.launcher.impl;
    8.22 +
    8.23 +import java.io.IOException;
    8.24 +import java.io.InputStream;
    8.25 +import java.lang.reflect.InvocationTargetException;
    8.26 +import java.lang.reflect.Method;
    8.27 +import java.lang.reflect.Modifier;
    8.28 +import java.net.URL;
    8.29 +import java.util.Enumeration;
    8.30 +import org.apidesign.bck2brwsr.core.JavaScriptBody;
    8.31 +
    8.32 +/**
    8.33 + *
    8.34 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    8.35 + */
    8.36 +public class Console {
    8.37 +    private Console() {
    8.38 +    }
    8.39 +    static {
    8.40 +        turnAssetionStatusOn();
    8.41 +    }
    8.42 +    
    8.43 +    @JavaScriptBody(args = {"id", "attr"}, body = 
    8.44 +        "return window.document.getElementById(id)[attr].toString();")
    8.45 +    private static native Object getAttr(String id, String attr);
    8.46 +
    8.47 +    @JavaScriptBody(args = {"id", "attr", "value"}, body = 
    8.48 +        "window.document.getElementById(id)[attr] = value;")
    8.49 +    private static native void setAttr(String id, String attr, Object value);
    8.50 +    
    8.51 +    @JavaScriptBody(args = {}, body = "return; window.close();")
    8.52 +    private static native void closeWindow();
    8.53 +
    8.54 +    private static void log(String newText) {
    8.55 +        String id = "bck2brwsr.result";
    8.56 +        String attr = "value";
    8.57 +        setAttr(id, attr, getAttr(id, attr) + "\n" + newText);
    8.58 +        setAttr(id, "scrollTop", getAttr(id, "scrollHeight"));
    8.59 +    }
    8.60 +    
    8.61 +    public static void execute() throws Exception {
    8.62 +        String clazz = (String) getAttr("clazz", "value");
    8.63 +        String method = (String) getAttr("method", "value");
    8.64 +        Object res = invokeMethod(clazz, method);
    8.65 +        setAttr("bck2brwsr.result", "value", res);
    8.66 +    }
    8.67 +
    8.68 +    @JavaScriptBody(args = { "url", "callback", "arr" }, body = ""
    8.69 +        + "var request = new XMLHttpRequest();\n"
    8.70 +        + "request.open('GET', url, true);\n"
    8.71 +        + "request.onreadystatechange = function() {\n"
    8.72 +        + "  if (this.readyState!==4) return;\n"
    8.73 +        + "  arr[0] = this.responseText;\n"
    8.74 +        + "  callback.run__V();\n"
    8.75 +        + "};"
    8.76 +        + "request.send();"
    8.77 +    )
    8.78 +    private static native void loadText(String url, Runnable callback, String[] arr) throws IOException;
    8.79 +    
    8.80 +    public static void harness(String url) throws IOException {
    8.81 +        log("Connecting to " + url);
    8.82 +        Request r = new Request(url);
    8.83 +    }
    8.84 +    
    8.85 +    private static class Request implements Runnable {
    8.86 +        private final String[] arr = { null };
    8.87 +        private final String url;
    8.88 +
    8.89 +        private Request(String url) throws IOException {
    8.90 +            this.url = url;
    8.91 +            loadText(url, this, arr);
    8.92 +        }
    8.93 +        
    8.94 +        @Override
    8.95 +        public void run() {
    8.96 +            try {
    8.97 +                String data = arr[0];
    8.98 +                log("\nGot \"" + data + "\"");
    8.99 +                
   8.100 +                if (data == null) {
   8.101 +                    log("Some error exiting");
   8.102 +                    closeWindow();
   8.103 +                    return;
   8.104 +                }
   8.105 +                
   8.106 +                if (data.isEmpty()) {
   8.107 +                    log("No data, exiting");
   8.108 +                    closeWindow();
   8.109 +                    return;
   8.110 +                }
   8.111 +                
   8.112 +                Case c = Case.parseData(data);
   8.113 +                if (c.getHtmlFragment() != null) {
   8.114 +                    setAttr("bck2brwsr.fragment", "innerHTML", c.getHtmlFragment());
   8.115 +                }
   8.116 +                log("Invoking " + c.getClassName() + '.' + c.getMethodName() + " as request: " + c.getRequestId());
   8.117 +
   8.118 +                Object result = invokeMethod(c.getClassName(), c.getMethodName());
   8.119 +                
   8.120 +                setAttr("bck2brwsr.fragment", "innerHTML", "");
   8.121 +                log("Result: " + result);
   8.122 +                
   8.123 +                result = encodeURL("" + result);
   8.124 +                
   8.125 +                log("Sending back: " + url + "?request=" + c.getRequestId() + "&result=" + result);
   8.126 +                String u = url + "?request=" + c.getRequestId() + "&result=" + result;
   8.127 +                
   8.128 +                loadText(u, this, arr);
   8.129 +                
   8.130 +            } catch (Exception ex) {
   8.131 +                log(ex.getMessage());
   8.132 +            }
   8.133 +        }
   8.134 +    }
   8.135 +    
   8.136 +    private static String encodeURL(String r) {
   8.137 +        StringBuilder sb = new StringBuilder();
   8.138 +        for (int i = 0; i < r.length(); i++) {
   8.139 +            int ch = r.charAt(i);
   8.140 +            if (ch < 32 || ch == '%' || ch == '+') {
   8.141 +                sb.append("%").append(("0" + Integer.toHexString(ch)).substring(0, 2));
   8.142 +            } else {
   8.143 +                if (ch == 32) {
   8.144 +                    sb.append("+");
   8.145 +                } else {
   8.146 +                    sb.append((char)ch);
   8.147 +                }
   8.148 +            }
   8.149 +        }
   8.150 +        return sb.toString();
   8.151 +    }
   8.152 +    
   8.153 +    static String invoke(String clazz, String method) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException {
   8.154 +        final Object r = invokeMethod(clazz, method);
   8.155 +        return r == null ? "null" : r.toString().toString();
   8.156 +    }
   8.157 +
   8.158 +    /** Helper method that inspects the classpath and loads given resource
   8.159 +     * (usually a class file). Used while running tests in Rhino.
   8.160 +     * 
   8.161 +     * @param name resource name to find
   8.162 +     * @return the array of bytes in the given resource
   8.163 +     * @throws IOException I/O in case something goes wrong
   8.164 +     */
   8.165 +    public static byte[] read(String name) throws IOException {
   8.166 +        URL u = null;
   8.167 +        Enumeration<URL> en = Console.class.getClassLoader().getResources(name);
   8.168 +        while (en.hasMoreElements()) {
   8.169 +            u = en.nextElement();
   8.170 +        }
   8.171 +        if (u == null) {
   8.172 +            throw new IOException("Can't find " + name);
   8.173 +        }
   8.174 +        try (InputStream is = u.openStream()) {
   8.175 +            byte[] arr;
   8.176 +            arr = new byte[is.available()];
   8.177 +            int offset = 0;
   8.178 +            while (offset < arr.length) {
   8.179 +                int len = is.read(arr, offset, arr.length - offset);
   8.180 +                if (len == -1) {
   8.181 +                    throw new IOException("Can't read " + name);
   8.182 +                }
   8.183 +                offset += len;
   8.184 +            }
   8.185 +            return arr;
   8.186 +        }
   8.187 +    }
   8.188 +   
   8.189 +    private static Object invokeMethod(String clazz, String method) 
   8.190 +    throws ClassNotFoundException, InvocationTargetException, 
   8.191 +    SecurityException, IllegalAccessException, IllegalArgumentException,
   8.192 +    InstantiationException {
   8.193 +        Method found = null;
   8.194 +        Class<?> c = Class.forName(clazz);
   8.195 +        for (Method m : c.getMethods()) {
   8.196 +            if (m.getName().equals(method)) {
   8.197 +                found = m;
   8.198 +            }
   8.199 +        }
   8.200 +        Object res;
   8.201 +        if (found != null) {
   8.202 +            try {
   8.203 +                if ((found.getModifiers() & Modifier.STATIC) != 0) {
   8.204 +                    res = found.invoke(null);
   8.205 +                } else {
   8.206 +                    res = found.invoke(c.newInstance());
   8.207 +                }
   8.208 +            } catch (Throwable ex) {
   8.209 +                res = ex.getClass().getName() + ":" + ex.getMessage();
   8.210 +            }
   8.211 +        } else {
   8.212 +            res = "Can't find method " + method + " in " + clazz;
   8.213 +        }
   8.214 +        return res;
   8.215 +    }
   8.216 +
   8.217 +    @JavaScriptBody(args = {}, body = "vm.desiredAssertionStatus = true;")
   8.218 +    private static void turnAssetionStatusOn() {
   8.219 +    }
   8.220 +    
   8.221 +    private static final class Case {
   8.222 +        private final Object data;
   8.223 +
   8.224 +        private Case(Object data) {
   8.225 +            this.data = data;
   8.226 +        }
   8.227 +        
   8.228 +        public static Case parseData(String s) {
   8.229 +            return new Case(toJSON(s));
   8.230 +        }
   8.231 +        
   8.232 +        public String getMethodName() {
   8.233 +            return value("methodName", data);
   8.234 +        }
   8.235 +
   8.236 +        public String getClassName() {
   8.237 +            return value("className", data);
   8.238 +        }
   8.239 +        
   8.240 +        public String getRequestId() {
   8.241 +            return value("request", data);
   8.242 +        }
   8.243 +
   8.244 +        public String getHtmlFragment() {
   8.245 +            return value("html", data);
   8.246 +        }
   8.247 +        
   8.248 +        @JavaScriptBody(args = "s", body = "return eval('(' + s + ')');")
   8.249 +        private static native Object toJSON(String s);
   8.250 +        
   8.251 +        @JavaScriptBody(args = {"p", "d"}, body = 
   8.252 +              "var v = d[p];\n"
   8.253 +            + "if (typeof v === 'undefined') return null;\n"
   8.254 +            + "return v.toString();"
   8.255 +        )
   8.256 +        private static native String value(String p, Object d);
   8.257 +    }
   8.258 +}
     9.1 --- a/launcher/src/main/resources/org/apidesign/bck2brwsr/launcher/harness.xhtml	Thu Jan 31 16:13:04 2013 +0100
     9.2 +++ b/launcher/src/main/resources/org/apidesign/bck2brwsr/launcher/harness.xhtml	Thu Jan 31 16:58:27 2013 +0100
     9.3 @@ -34,7 +34,7 @@
     9.4          <div id="bck2brwsr.fragment"/>
     9.5          
     9.6          <script type="text/javascript">
     9.7 -            vm.loadClass('org.apidesign.bck2brwsr.launcher.Console').harness__VLjava_lang_String_2('$U/../data');
     9.8 +            vm.loadClass('org.apidesign.bck2brwsr.launcher.impl.Console').harness__VLjava_lang_String_2('$U/../data');
     9.9          </script>
    9.10      </body>
    9.11  </html>
    10.1 --- a/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java	Thu Jan 31 16:13:04 2013 +0100
    10.2 +++ b/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java	Thu Jan 31 16:58:27 2013 +0100
    10.3 @@ -24,7 +24,7 @@
    10.4  import java.lang.reflect.InvocationTargetException;
    10.5  import java.lang.reflect.Method;
    10.6  import org.apidesign.bck2brwsr.launcher.Launcher;
    10.7 -import org.apidesign.bck2brwsr.launcher.MethodInvocation;
    10.8 +import org.apidesign.bck2brwsr.launcher.InvocationContext;
    10.9  import org.testng.ITest;
   10.10  import org.testng.annotations.Test;
   10.11  
   10.12 @@ -51,8 +51,11 @@
   10.13      @Test(groups = "run")
   10.14      public void executeCode() throws Throwable {
   10.15          if (l != null) {
   10.16 -            MethodInvocation c = l.invokeMethod(m.getDeclaringClass(), m.getName(), html);
   10.17 -            String res = c.toString();
   10.18 +            InvocationContext c = l.createInvocation(m.getDeclaringClass(), m.getName());
   10.19 +            if (html != null) {
   10.20 +                c.setHtmlFragment(html);
   10.21 +            }
   10.22 +            String res = c.invoke();
   10.23              value = res;
   10.24              if (fail) {
   10.25                  int idx = res.indexOf(':');