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