launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java
branchmodel
changeset 1041 f18b7262fe91
parent 1033 b8773b7b9ecd
child 1043 bd80952bfd11
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java	Sun Apr 28 17:42:49 2013 +0200
     1.3 @@ -0,0 +1,415 @@
     1.4 +/**
     1.5 + * Back 2 Browser Bytecode Translator
     1.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     1.7 + *
     1.8 + * This program is free software: you can redistribute it and/or modify
     1.9 + * it under the terms of the GNU General Public License as published by
    1.10 + * the Free Software Foundation, version 2 of the License.
    1.11 + *
    1.12 + * This program is distributed in the hope that it will be useful,
    1.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.15 + * GNU General Public License for more details.
    1.16 + *
    1.17 + * You should have received a copy of the GNU General Public License
    1.18 + * along with this program. Look for COPYING file in the top folder.
    1.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    1.20 + */
    1.21 +package org.apidesign.bck2brwsr.launcher.fximpl;
    1.22 +
    1.23 +import java.io.IOException;
    1.24 +import java.io.InputStream;
    1.25 +import java.io.UnsupportedEncodingException;
    1.26 +import java.lang.reflect.InvocationTargetException;
    1.27 +import java.lang.reflect.Method;
    1.28 +import java.lang.reflect.Modifier;
    1.29 +import java.net.URL;
    1.30 +import java.util.Enumeration;
    1.31 +import javafx.scene.web.WebEngine;
    1.32 +import netscape.javascript.JSObject;
    1.33 +import org.apidesign.bck2brwsr.core.JavaScriptBody;
    1.34 +
    1.35 +/**
    1.36 + *
    1.37 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    1.38 + */
    1.39 +public final class Console {
    1.40 +    public Console() {
    1.41 +    }
    1.42 +    
    1.43 +    @JavaScriptBody(args = {"elem", "attr"}, body = 
    1.44 +        "return elem[attr].toString();")
    1.45 +    private static Object getAttr(Object elem, String attr) {
    1.46 +        return InvokeJS.CObject.call("getAttr", elem, attr);
    1.47 +    }
    1.48 +
    1.49 +    @JavaScriptBody(args = {"id", "attr", "value"}, body = 
    1.50 +        "window.document.getElementById(id)[attr] = value;")
    1.51 +    private static void setAttr(String id, String attr, Object value) {
    1.52 +        InvokeJS.CObject.call("setAttrId", id, attr, value);
    1.53 +    }
    1.54 +    @JavaScriptBody(args = {"elem", "attr", "value"}, body = 
    1.55 +        "elem[attr] = value;")
    1.56 +    private static void setAttr(Object id, String attr, Object value) {
    1.57 +        InvokeJS.CObject.call("setAttr", id, attr, value);
    1.58 +    }
    1.59 +    
    1.60 +    @JavaScriptBody(args = {}, body = "return; window.close();")
    1.61 +    private static void closeWindow() {}
    1.62 +
    1.63 +    private static Object textArea;
    1.64 +    private static Object statusArea;
    1.65 +    
    1.66 +    private static void log(String newText) {
    1.67 +        if (textArea == null) {
    1.68 +            return;
    1.69 +        }
    1.70 +        String attr = "value";
    1.71 +        setAttr(textArea, attr, getAttr(textArea, attr) + "\n" + newText);
    1.72 +        setAttr(textArea, "scrollTop", getAttr(textArea, "scrollHeight"));
    1.73 +    }
    1.74 +    
    1.75 +    private static void beginTest(Case c) {
    1.76 +        Object[] arr = new Object[2];
    1.77 +        beginTest(c.getClassName() + "." + c.getMethodName(), c, arr);
    1.78 +        textArea = arr[0];
    1.79 +        statusArea = arr[1];
    1.80 +    }
    1.81 +    
    1.82 +    private static void finishTest(Case c, Object res) {
    1.83 +        if ("null".equals(res)) {
    1.84 +            setAttr(statusArea, "innerHTML", "Success");
    1.85 +        } else {
    1.86 +            setAttr(statusArea, "innerHTML", "Result " + res);
    1.87 +        }
    1.88 +        statusArea = null;
    1.89 +        textArea = null;
    1.90 +    }
    1.91 +
    1.92 +    private static final String BEGIN_TEST =  
    1.93 +        "var ul = window.document.getElementById('bck2brwsr.result');\n"
    1.94 +        + "var li = window.document.createElement('li');\n"
    1.95 +        + "var span = window.document.createElement('span');"
    1.96 +        + "span.innerHTML = test + ' - ';\n"
    1.97 +        + "var details = window.document.createElement('a');\n"
    1.98 +        + "details.innerHTML = 'Details';\n"
    1.99 +        + "details.href = '#';\n"
   1.100 +        + "var p = window.document.createElement('p');\n"
   1.101 +        + "var status = window.document.createElement('a');\n"
   1.102 +        + "status.innerHTML = 'running';"
   1.103 +        + "details.onclick = function() { li.appendChild(p); li.removeChild(details); status.innerHTML = 'Run Again'; status.href = '#'; };\n"
   1.104 +        + "status.onclick = function() { c.again(arr); }\n"
   1.105 +        + "var pre = window.document.createElement('textarea');\n"
   1.106 +        + "pre.cols = 100;"
   1.107 +        + "pre.rows = 10;"
   1.108 +        + "li.appendChild(span);\n"
   1.109 +        + "li.appendChild(status);\n"
   1.110 +        + "var span = window.document.createElement('span');"
   1.111 +        + "span.innerHTML = ' ';\n"
   1.112 +        + "li.appendChild(span);\n"
   1.113 +        + "li.appendChild(details);\n"
   1.114 +        + "p.appendChild(pre);\n"
   1.115 +        + "ul.appendChild(li);\n"
   1.116 +        + "arr[0] = pre;\n"
   1.117 +        + "arr[1] = status;\n";
   1.118 +        
   1.119 +    @JavaScriptBody(args = { "test", "c", "arr" }, body = BEGIN_TEST)
   1.120 +    private static void beginTest(String test, Case c, Object[] arr) {
   1.121 +        InvokeJS.CObject.call("beginTest", test, c, arr);
   1.122 +    }
   1.123 +    
   1.124 +    private static final String LOAD_TEXT = 
   1.125 +          "var request = new XMLHttpRequest();\n"
   1.126 +        + "request.open('GET', url, true);\n"
   1.127 +        + "request.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');\n"
   1.128 +        + "request.onreadystatechange = function() {\n"
   1.129 +        + "  if (this.readyState!==4) return;\n"
   1.130 +        + " try {"
   1.131 +        + "  arr[0] = this.responseText;\n"
   1.132 +        + "  callback.run__V();\n"
   1.133 +        + " } catch (e) { alert(e); }"
   1.134 +        + "};"
   1.135 +        + "request.send();";
   1.136 +    @JavaScriptBody(args = { "url", "callback", "arr" }, body = LOAD_TEXT)
   1.137 +    private static void loadText(String url, Runnable callback, String[] arr) throws IOException {
   1.138 +        InvokeJS.CObject.call("loadText", url, new Run(callback), arr);
   1.139 +    }
   1.140 +    
   1.141 +    public static void runHarness(String url) throws IOException {
   1.142 +        new Console().harness(url);
   1.143 +    }
   1.144 +    
   1.145 +    public void harness(String url) throws IOException {
   1.146 +        log("Connecting to " + url);
   1.147 +        Request r = new Request(url);
   1.148 +    }
   1.149 +    
   1.150 +    private static class Request implements Runnable {
   1.151 +        private final String[] arr = { null };
   1.152 +        private final String url;
   1.153 +        private Case c;
   1.154 +        private int retries;
   1.155 +
   1.156 +        private Request(String url) throws IOException {
   1.157 +            this.url = url;
   1.158 +            loadText(url, this, arr);
   1.159 +        }
   1.160 +        private Request(String url, String u) throws IOException {
   1.161 +            this.url = url;
   1.162 +            loadText(u, this, arr);
   1.163 +        }
   1.164 +        
   1.165 +        @Override
   1.166 +        public void run() {
   1.167 +            try {
   1.168 +                if (c == null) {
   1.169 +                    String data = arr[0];
   1.170 +
   1.171 +                    if (data == null) {
   1.172 +                        log("Some error exiting");
   1.173 +                        closeWindow();
   1.174 +                        return;
   1.175 +                    }
   1.176 +
   1.177 +                    if (data.isEmpty()) {
   1.178 +                        log("No data, exiting");
   1.179 +                        closeWindow();
   1.180 +                        return;
   1.181 +                    }
   1.182 +
   1.183 +                    c = Case.parseData(data);
   1.184 +                    beginTest(c);
   1.185 +                    log("Got \"" + data + "\"");
   1.186 +                } else {
   1.187 +                    log("Processing \"" + arr[0] + "\" for " + retries + " time");
   1.188 +                }
   1.189 +                Object result = retries++ >= 10 ? "java.lang.InterruptedException:timeout" : c.runTest();
   1.190 +                finishTest(c, result);
   1.191 +                
   1.192 +                String u = url + "?request=" + c.getRequestId() + "&result=" + result;
   1.193 +                new Request(url, u);
   1.194 +            } catch (Exception ex) {
   1.195 +                if (ex instanceof InterruptedException) {
   1.196 +                    log("Re-scheduling in 100ms");
   1.197 +                    schedule(this, 100);
   1.198 +                    return;
   1.199 +                }
   1.200 +                log(ex.getClass().getName() + ":" + ex.getMessage());
   1.201 +            }
   1.202 +        }
   1.203 +    }
   1.204 +    
   1.205 +    private static String encodeURL(String r) throws UnsupportedEncodingException {
   1.206 +        final String SPECIAL = "%$&+,/:;=?@";
   1.207 +        StringBuilder sb = new StringBuilder();
   1.208 +        byte[] utf8 = r.getBytes("UTF-8");
   1.209 +        for (int i = 0; i < utf8.length; i++) {
   1.210 +            int ch = utf8[i] & 0xff;
   1.211 +            if (ch < 32 || ch > 127 || SPECIAL.indexOf(ch) >= 0) {
   1.212 +                final String numbers = "0" + Integer.toHexString(ch);
   1.213 +                sb.append("%").append(numbers.substring(numbers.length() - 2));
   1.214 +            } else {
   1.215 +                if (ch == 32) {
   1.216 +                    sb.append("+");
   1.217 +                } else {
   1.218 +                    sb.append((char)ch);
   1.219 +                }
   1.220 +            }
   1.221 +        }
   1.222 +        return sb.toString();
   1.223 +    }
   1.224 +    
   1.225 +    static String invoke(String clazz, String method) throws 
   1.226 +    ClassNotFoundException, InvocationTargetException, IllegalAccessException, 
   1.227 +    InstantiationException, InterruptedException {
   1.228 +        final Object r = new Case(null).invokeMethod(clazz, method);
   1.229 +        return r == null ? "null" : r.toString().toString();
   1.230 +    }
   1.231 +
   1.232 +    /** Helper method that inspects the classpath and loads given resource
   1.233 +     * (usually a class file). Used while running tests in Rhino.
   1.234 +     * 
   1.235 +     * @param name resource name to find
   1.236 +     * @return the array of bytes in the given resource
   1.237 +     * @throws IOException I/O in case something goes wrong
   1.238 +     */
   1.239 +    public static byte[] read(String name) throws IOException {
   1.240 +        URL u = null;
   1.241 +        Enumeration<URL> en = Console.class.getClassLoader().getResources(name);
   1.242 +        while (en.hasMoreElements()) {
   1.243 +            u = en.nextElement();
   1.244 +        }
   1.245 +        if (u == null) {
   1.246 +            throw new IOException("Can't find " + name);
   1.247 +        }
   1.248 +        try (InputStream is = u.openStream()) {
   1.249 +            byte[] arr;
   1.250 +            arr = new byte[is.available()];
   1.251 +            int offset = 0;
   1.252 +            while (offset < arr.length) {
   1.253 +                int len = is.read(arr, offset, arr.length - offset);
   1.254 +                if (len == -1) {
   1.255 +                    throw new IOException("Can't read " + name);
   1.256 +                }
   1.257 +                offset += len;
   1.258 +            }
   1.259 +            return arr;
   1.260 +        }
   1.261 +    }
   1.262 +   
   1.263 +    @JavaScriptBody(args = {}, body = "vm.desiredAssertionStatus = true;")
   1.264 +    private static void turnAssetionStatusOn() {
   1.265 +    }
   1.266 +
   1.267 +    @JavaScriptBody(args = {"r", "time"}, body =
   1.268 +        "return window.setTimeout(function() { r.run__V(); }, time);")
   1.269 +    private static Object schedule(Runnable r, int time) {
   1.270 +        return InvokeJS.CObject.call("schedule", new Run(r), time);
   1.271 +    }
   1.272 +    
   1.273 +    private static final class Case {
   1.274 +        private final Object data;
   1.275 +        private Object inst;
   1.276 +
   1.277 +        private Case(Object data) {
   1.278 +            this.data = data;
   1.279 +        }
   1.280 +        
   1.281 +        public static Case parseData(String s) {
   1.282 +            return new Case(toJSON(s));
   1.283 +        }
   1.284 +        
   1.285 +        public String getMethodName() {
   1.286 +            return (String) value("methodName", data);
   1.287 +        }
   1.288 +
   1.289 +        public String getClassName() {
   1.290 +            return (String) value("className", data);
   1.291 +        }
   1.292 +        
   1.293 +        public int getRequestId() {
   1.294 +            Object v = value("request", data);
   1.295 +            if (v instanceof Number) {
   1.296 +                return ((Number)v).intValue();
   1.297 +            }
   1.298 +            return Integer.parseInt(v.toString());
   1.299 +        }
   1.300 +
   1.301 +        public String getHtmlFragment() {
   1.302 +            return (String) value("html", data);
   1.303 +        }
   1.304 +        
   1.305 +        void again(Object[] arr) {
   1.306 +            try {
   1.307 +                textArea = arr[0];
   1.308 +                statusArea = arr[1];
   1.309 +                setAttr(textArea, "value", "");
   1.310 +                runTest();
   1.311 +            } catch (Exception ex) {
   1.312 +                log(ex.getClass().getName() + ":" + ex.getMessage());
   1.313 +            }
   1.314 +        }
   1.315 +
   1.316 +        private Object runTest() throws IllegalAccessException, 
   1.317 +        IllegalArgumentException, ClassNotFoundException, UnsupportedEncodingException, 
   1.318 +        InvocationTargetException, InstantiationException, InterruptedException {
   1.319 +            if (this.getHtmlFragment() != null) {
   1.320 +                setAttr("bck2brwsr.fragment", "innerHTML", this.getHtmlFragment());
   1.321 +            }
   1.322 +            log("Invoking " + this.getClassName() + '.' + this.getMethodName() + " as request: " + this.getRequestId());
   1.323 +            Object result = invokeMethod(this.getClassName(), this.getMethodName());
   1.324 +            setAttr("bck2brwsr.fragment", "innerHTML", "");
   1.325 +            log("Result: " + result);
   1.326 +            result = encodeURL("" + result);
   1.327 +            log("Sending back: ...?request=" + this.getRequestId() + "&result=" + result);
   1.328 +            return result;
   1.329 +        }
   1.330 +
   1.331 +        private Object invokeMethod(String clazz, String method)
   1.332 +        throws ClassNotFoundException, InvocationTargetException,
   1.333 +        InterruptedException, IllegalAccessException, IllegalArgumentException,
   1.334 +        InstantiationException {
   1.335 +            Method found = null;
   1.336 +            Class<?> c = Class.forName(clazz);
   1.337 +            for (Method m : c.getMethods()) {
   1.338 +                if (m.getName().equals(method)) {
   1.339 +                    found = m;
   1.340 +                }
   1.341 +            }
   1.342 +            Object res;
   1.343 +            if (found != null) {
   1.344 +                try {
   1.345 +                    if ((found.getModifiers() & Modifier.STATIC) != 0) {
   1.346 +                        res = found.invoke(null);
   1.347 +                    } else {
   1.348 +                        if (inst == null) {
   1.349 +                            inst = c.newInstance();
   1.350 +                        }
   1.351 +                        res = found.invoke(inst);
   1.352 +                    }
   1.353 +                } catch (Throwable ex) {
   1.354 +                    if (ex instanceof InvocationTargetException) {
   1.355 +                        ex = ((InvocationTargetException) ex).getTargetException();
   1.356 +                    }
   1.357 +                    if (ex instanceof InterruptedException) {
   1.358 +                        throw (InterruptedException)ex;
   1.359 +                    }
   1.360 +                    res = ex.getClass().getName() + ":" + ex.getMessage();
   1.361 +                }
   1.362 +            } else {
   1.363 +                res = "Can't find method " + method + " in " + clazz;
   1.364 +            }
   1.365 +            return res;
   1.366 +        }
   1.367 +        
   1.368 +        @JavaScriptBody(args = "s", body = "return eval('(' + s + ')');")
   1.369 +        private static Object toJSON(String s) {
   1.370 +            return InvokeJS.CObject.call("toJSON", s);
   1.371 +        }
   1.372 +        
   1.373 +        @JavaScriptBody(args = {"p", "d"}, body = 
   1.374 +              "var v = d[p];\n"
   1.375 +            + "if (typeof v === 'undefined') return null;\n"
   1.376 +            + "return v.toString();"
   1.377 +        )
   1.378 +        private static Object value(String p, Object d) {
   1.379 +            return ((JSObject)d).getMember(p);
   1.380 +        }
   1.381 +    }
   1.382 +    
   1.383 +    private static String safe(String txt) {
   1.384 +        return "try {" + txt + "} catch (err) { alert(err); }";
   1.385 +    }
   1.386 +    
   1.387 +    static {
   1.388 +        turnAssetionStatusOn();
   1.389 +    }
   1.390 +    
   1.391 +    private static final class InvokeJS {
   1.392 +        static final JSObject CObject = initJS();
   1.393 +
   1.394 +        @JavaScriptBody(args = {  }, body = "return null;")
   1.395 +        private static JSObject initJS() {
   1.396 +            WebEngine web = (WebEngine) System.getProperties().get("webEngine");
   1.397 +            return (JSObject) web.executeScript("(function() {"
   1.398 +                + "var CObject = {};"
   1.399 +
   1.400 +                + "CObject.getAttr = function(elem, attr) { return elem[attr].toString(); };"
   1.401 +
   1.402 +                + "CObject.setAttrId = function(id, attr, value) { window.document.getElementById(id)[attr] = value; };"
   1.403 +                + "CObject.setAttr = function(elem, attr, value) { elem[attr] = value; };"
   1.404 +
   1.405 +                + "CObject.beginTest = function(test, c, arr) {" + safe(BEGIN_TEST) + "};"
   1.406 +
   1.407 +                + "CObject.loadText = function(url, callback, arr) {" + safe(LOAD_TEXT.replace("run__V", "run")) + "};"
   1.408 +
   1.409 +                + "CObject.schedule = function(r, time) { return window.setTimeout(function() { r.run(); }, time); };"
   1.410 +
   1.411 +                + "CObject.toJSON = function(s) { return eval('(' + s + ')'); };"
   1.412 +
   1.413 +                + "return CObject;"
   1.414 +            + "})(this)");
   1.415 +        }
   1.416 +    }
   1.417 +    
   1.418 +}