rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Wed, 17 Apr 2013 17:04:40 +0200
branchfx
changeset 1004 04efef2a9c1e
parent 922 2fb3e929962f
parent 942 0e2ced48871d
child 1006 691c5cd3fb93
permissions -rw-r--r--
Rather than piggybacking on first alert call, use the fact that the server and FX Web View are in the same VM and notify the view that bck2brwsr.js is about to be served from the server.
     1 /**
     2  * Back 2 Browser Bytecode Translator
     3  * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4  *
     5  * This program is free software: you can redistribute it and/or modify
     6  * it under the terms of the GNU General Public License as published by
     7  * the Free Software Foundation, version 2 of the License.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with this program. Look for COPYING file in the top folder.
    16  * If not, see http://opensource.org/licenses/GPL-2.0.
    17  */
    18 package org.apidesign.bck2brwsr.launcher.impl;
    19 
    20 import java.io.IOException;
    21 import java.io.InputStream;
    22 import java.io.UnsupportedEncodingException;
    23 import java.lang.reflect.InvocationTargetException;
    24 import java.lang.reflect.Method;
    25 import java.lang.reflect.Modifier;
    26 import java.net.URL;
    27 import java.util.Enumeration;
    28 import org.apidesign.bck2brwsr.core.JavaScriptBody;
    29 
    30 /**
    31  *
    32  * @author Jaroslav Tulach <jtulach@netbeans.org>
    33  */
    34 public class Console {
    35     private Console() {
    36     }
    37     static {
    38         turnAssetionStatusOn();
    39     }
    40     
    41     @JavaScriptBody(args = {"id", "attr"}, body = 
    42         "return window.document.getElementById(id)[attr].toString();")
    43     private static native Object getAttr(String id, String attr);
    44     @JavaScriptBody(args = {"elem", "attr"}, body = 
    45         "return elem[attr].toString();")
    46     private static native Object getAttr(Object elem, String attr);
    47 
    48     @JavaScriptBody(args = {"id", "attr", "value"}, body = 
    49         "window.document.getElementById(id)[attr] = value;")
    50     private static native void setAttr(String id, String attr, Object value);
    51     @JavaScriptBody(args = {"elem", "attr", "value"}, body = 
    52         "elem[attr] = value;")
    53     private static native void setAttr(Object id, String attr, Object value);
    54     
    55     @JavaScriptBody(args = {}, body = "return; window.close();")
    56     private static native void closeWindow();
    57 
    58     private static Object textArea;
    59     private static Object statusArea;
    60     
    61     private static void log(String newText) {
    62         if (textArea == null) {
    63             return;
    64         }
    65         String attr = "value";
    66         setAttr(textArea, attr, getAttr(textArea, attr) + "\n" + newText);
    67         setAttr(textArea, "scrollTop", getAttr(textArea, "scrollHeight"));
    68     }
    69     
    70     private static void beginTest(Case c) {
    71         Object[] arr = new Object[2];
    72         beginTest(c.getClassName() + "." + c.getMethodName(), c, arr);
    73         textArea = arr[0];
    74         statusArea = arr[1];
    75     }
    76     
    77     private static void finishTest(Case c, Object res) {
    78         if ("null".equals(res)) {
    79             setAttr(statusArea, "innerHTML", "Success");
    80         } else {
    81             setAttr(statusArea, "innerHTML", "Result " + res);
    82         }
    83         statusArea = null;
    84         textArea = null;
    85     }
    86 
    87     @JavaScriptBody(args = { "test", "c", "arr" }, body = 
    88           "var ul = window.document.getElementById('bck2brwsr.result');\n"
    89         + "var li = window.document.createElement('li');\n"
    90         + "var span = window.document.createElement('span');"
    91         + "span.innerHTML = test + ' - ';\n"
    92         + "var details = window.document.createElement('a');\n"
    93         + "details.innerHTML = 'Details';\n"
    94         + "details.href = '#';\n"
    95         + "var p = window.document.createElement('p');\n"
    96         + "var status = window.document.createElement('a');\n"
    97         + "status.innerHTML = 'running';"
    98         + "details.onclick = function() { li.appendChild(p); li.removeChild(details); status.innerHTML = 'Run Again'; status.href = '#'; };\n"
    99         + "status.onclick = function() { c.again__V_3Ljava_lang_Object_2(arr); }\n"
   100         + "var pre = window.document.createElement('textarea');\n"
   101         + "pre.cols = 100;"
   102         + "pre.rows = 10;"
   103         + "li.appendChild(span);\n"
   104         + "li.appendChild(status);\n"
   105         + "var span = window.document.createElement('span');"
   106         + "span.innerHTML = ' ';\n"
   107         + "li.appendChild(span);\n"
   108         + "li.appendChild(details);\n"
   109         + "p.appendChild(pre);\n"
   110         + "ul.appendChild(li);\n"
   111         + "arr[0] = pre;\n"
   112         + "arr[1] = status;\n"
   113     )
   114     private static native void beginTest(String test, Case c, Object[] arr);
   115     
   116     @JavaScriptBody(args = { "url", "callback", "arr" }, body = ""
   117         + "var request = new XMLHttpRequest();\n"
   118         + "request.open('GET', url, true);\n"
   119         + "request.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');\n"
   120         + "request.onreadystatechange = function() {\n"
   121         + "  if (this.readyState!==4) return;\n"
   122         + "  arr[0] = this.responseText;\n"
   123         + "  callback.run__V();\n"
   124         + "};"
   125         + "request.send();"
   126     )
   127     private static native void loadText(String url, Runnable callback, String[] arr) throws IOException;
   128     
   129     public static void harness(String url) throws IOException {
   130         log("Connecting to " + url);
   131         Request r = new Request(url);
   132     }
   133     
   134     private static class Request implements Runnable {
   135         private final String[] arr = { null };
   136         private final String url;
   137         private Case c;
   138         private int retries;
   139 
   140         private Request(String url) throws IOException {
   141             this.url = url;
   142             loadText(url, this, arr);
   143         }
   144         private Request(String url, String u) throws IOException {
   145             this.url = url;
   146             loadText(u, this, arr);
   147         }
   148         
   149         @Override
   150         public void run() {
   151             try {
   152                 if (c == null) {
   153                     String data = arr[0];
   154 
   155                     if (data == null) {
   156                         log("Some error exiting");
   157                         closeWindow();
   158                         return;
   159                     }
   160 
   161                     if (data.isEmpty()) {
   162                         log("No data, exiting");
   163                         closeWindow();
   164                         return;
   165                     }
   166 
   167                     c = Case.parseData(data);
   168                     beginTest(c);
   169                     log("Got \"" + data + "\"");
   170                 } else {
   171                     log("Processing \"" + arr[0] + "\" for " + retries + " time");
   172                 }
   173                 Object result = retries++ >= 10 ? "java.lang.InterruptedException:timeout" : c.runTest();
   174                 finishTest(c, result);
   175                 
   176                 String u = url + "?request=" + c.getRequestId() + "&result=" + result;
   177                 new Request(url, u);
   178             } catch (Exception ex) {
   179                 if (ex instanceof InterruptedException) {
   180                     log("Re-scheduling in 100ms");
   181                     schedule(this, 100);
   182                     return;
   183                 }
   184                 log(ex.getClass().getName() + ":" + ex.getMessage());
   185             }
   186         }
   187     }
   188     
   189     private static String encodeURL(String r) throws UnsupportedEncodingException {
   190         final String SPECIAL = "%$&+,/:;=?@";
   191         StringBuilder sb = new StringBuilder();
   192         byte[] utf8 = r.getBytes("UTF-8");
   193         for (int i = 0; i < utf8.length; i++) {
   194             int ch = utf8[i] & 0xff;
   195             if (ch < 32 || ch > 127 || SPECIAL.indexOf(ch) >= 0) {
   196                 final String numbers = "0" + Integer.toHexString(ch);
   197                 sb.append("%").append(numbers.substring(numbers.length() - 2));
   198             } else {
   199                 if (ch == 32) {
   200                     sb.append("+");
   201                 } else {
   202                     sb.append((char)ch);
   203                 }
   204             }
   205         }
   206         return sb.toString();
   207     }
   208     
   209     static String invoke(String clazz, String method) throws 
   210     ClassNotFoundException, InvocationTargetException, IllegalAccessException, 
   211     InstantiationException, InterruptedException {
   212         final Object r = new Case(null).invokeMethod(clazz, method);
   213         return r == null ? "null" : r.toString().toString();
   214     }
   215 
   216     /** Helper method that inspects the classpath and loads given resource
   217      * (usually a class file). Used while running tests in Rhino.
   218      * 
   219      * @param name resource name to find
   220      * @return the array of bytes in the given resource
   221      * @throws IOException I/O in case something goes wrong
   222      */
   223     public static byte[] read(String name) throws IOException {
   224         URL u = null;
   225         Enumeration<URL> en = Console.class.getClassLoader().getResources(name);
   226         while (en.hasMoreElements()) {
   227             u = en.nextElement();
   228         }
   229         if (u == null) {
   230             throw new IOException("Can't find " + name);
   231         }
   232         try (InputStream is = u.openStream()) {
   233             byte[] arr;
   234             arr = new byte[is.available()];
   235             int offset = 0;
   236             while (offset < arr.length) {
   237                 int len = is.read(arr, offset, arr.length - offset);
   238                 if (len == -1) {
   239                     throw new IOException("Can't read " + name);
   240                 }
   241                 offset += len;
   242             }
   243             return arr;
   244         }
   245     }
   246    
   247     @JavaScriptBody(args = {}, body = "vm.desiredAssertionStatus = true;")
   248     private static void turnAssetionStatusOn() {
   249     }
   250 
   251     @JavaScriptBody(args = {"r", "time"}, body =
   252         "return window.setTimeout(function() { r.run__V(); }, time);")
   253     private static native Object schedule(Runnable r, int time);
   254     
   255     private static final class Case {
   256         private final Object data;
   257         private Object inst;
   258 
   259         private Case(Object data) {
   260             this.data = data;
   261         }
   262         
   263         public static Case parseData(String s) {
   264             return new Case(toJSON(s));
   265         }
   266         
   267         public String getMethodName() {
   268             return value("methodName", data);
   269         }
   270 
   271         public String getClassName() {
   272             return value("className", data);
   273         }
   274         
   275         public String getRequestId() {
   276             return value("request", data);
   277         }
   278 
   279         public String getHtmlFragment() {
   280             return value("html", data);
   281         }
   282         
   283         void again(Object[] arr) {
   284             try {
   285                 textArea = arr[0];
   286                 statusArea = arr[1];
   287                 setAttr(textArea, "value", "");
   288                 runTest();
   289             } catch (Exception ex) {
   290                 log(ex.getClass().getName() + ":" + ex.getMessage());
   291             }
   292         }
   293 
   294         private Object runTest() throws IllegalAccessException, 
   295         IllegalArgumentException, ClassNotFoundException, UnsupportedEncodingException, 
   296         InvocationTargetException, InstantiationException, InterruptedException {
   297             if (this.getHtmlFragment() != null) {
   298                 setAttr("bck2brwsr.fragment", "innerHTML", this.getHtmlFragment());
   299             }
   300             log("Invoking " + this.getClassName() + '.' + this.getMethodName() + " as request: " + this.getRequestId());
   301             Object result = invokeMethod(this.getClassName(), this.getMethodName());
   302             setAttr("bck2brwsr.fragment", "innerHTML", "");
   303             log("Result: " + result);
   304             result = encodeURL("" + result);
   305             log("Sending back: ...?request=" + this.getRequestId() + "&result=" + result);
   306             return result;
   307         }
   308 
   309         private Object invokeMethod(String clazz, String method)
   310         throws ClassNotFoundException, InvocationTargetException,
   311         InterruptedException, IllegalAccessException, IllegalArgumentException,
   312         InstantiationException {
   313             Method found = null;
   314             Class<?> c = Class.forName(clazz);
   315             for (Method m : c.getMethods()) {
   316                 if (m.getName().equals(method)) {
   317                     found = m;
   318                 }
   319             }
   320             Object res;
   321             if (found != null) {
   322                 try {
   323                     if ((found.getModifiers() & Modifier.STATIC) != 0) {
   324                         res = found.invoke(null);
   325                     } else {
   326                         if (inst == null) {
   327                             inst = c.newInstance();
   328                         }
   329                         res = found.invoke(inst);
   330                     }
   331                 } catch (Throwable ex) {
   332                     if (ex instanceof InvocationTargetException) {
   333                         ex = ((InvocationTargetException) ex).getTargetException();
   334                     }
   335                     if (ex instanceof InterruptedException) {
   336                         throw (InterruptedException)ex;
   337                     }
   338                     res = ex.getClass().getName() + ":" + ex.getMessage();
   339                 }
   340             } else {
   341                 res = "Can't find method " + method + " in " + clazz;
   342             }
   343             return res;
   344         }
   345         
   346         @JavaScriptBody(args = "s", body = "return eval('(' + s + ')');")
   347         private static native Object toJSON(String s);
   348         
   349         @JavaScriptBody(args = {"p", "d"}, body = 
   350               "var v = d[p];\n"
   351             + "if (typeof v === 'undefined') return null;\n"
   352             + "return v.toString();"
   353         )
   354         private static native String value(String p, Object d);
   355     }
   356 }