jaroslav@323: /** jaroslav@323: * Back 2 Browser Bytecode Translator jaroslav@323: * Copyright (C) 2012 Jaroslav Tulach jaroslav@323: * jaroslav@323: * This program is free software: you can redistribute it and/or modify jaroslav@323: * it under the terms of the GNU General Public License as published by jaroslav@323: * the Free Software Foundation, version 2 of the License. jaroslav@323: * jaroslav@323: * This program is distributed in the hope that it will be useful, jaroslav@323: * but WITHOUT ANY WARRANTY; without even the implied warranty of jaroslav@323: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the jaroslav@323: * GNU General Public License for more details. jaroslav@323: * jaroslav@323: * You should have received a copy of the GNU General Public License jaroslav@323: * along with this program. Look for COPYING file in the top folder. jaroslav@323: * If not, see http://opensource.org/licenses/GPL-2.0. jaroslav@323: */ jaroslav@622: package org.apidesign.bck2brwsr.launcher.impl; jaroslav@323: jaroslav@360: import java.io.IOException; jaroslav@360: import java.io.InputStream; jaroslav@802: import java.io.UnsupportedEncodingException; jaroslav@332: import java.lang.reflect.InvocationTargetException; jaroslav@323: import java.lang.reflect.Method; jaroslav@412: import java.lang.reflect.Modifier; jaroslav@332: import java.net.URL; jaroslav@360: import java.util.Enumeration; jaroslav@323: import org.apidesign.bck2brwsr.core.JavaScriptBody; jaroslav@323: jaroslav@323: /** jaroslav@323: * jaroslav@323: * @author Jaroslav Tulach jaroslav@323: */ jaroslav@1006: public final class Console { jaroslav@1006: public Console() { jaroslav@622: } jaroslav@517: static { jaroslav@517: turnAssetionStatusOn(); jaroslav@517: } jaroslav@323: jaroslav@323: @JavaScriptBody(args = {"id", "attr"}, body = jaroslav@323: "return window.document.getElementById(id)[attr].toString();") jaroslav@323: private static native Object getAttr(String id, String attr); jaroslav@916: @JavaScriptBody(args = {"elem", "attr"}, body = jaroslav@916: "return elem[attr].toString();") jaroslav@916: private static native Object getAttr(Object elem, String attr); jaroslav@323: jaroslav@323: @JavaScriptBody(args = {"id", "attr", "value"}, body = jaroslav@323: "window.document.getElementById(id)[attr] = value;") jaroslav@323: private static native void setAttr(String id, String attr, Object value); jaroslav@916: @JavaScriptBody(args = {"elem", "attr", "value"}, body = jaroslav@916: "elem[attr] = value;") jaroslav@916: private static native void setAttr(Object id, String attr, Object value); jaroslav@366: jaroslav@381: @JavaScriptBody(args = {}, body = "return; window.close();") jaroslav@366: private static native void closeWindow(); jaroslav@342: jaroslav@916: private static Object textArea; jaroslav@916: private static Object statusArea; jaroslav@916: jaroslav@343: private static void log(String newText) { jaroslav@916: if (textArea == null) { jaroslav@916: return; jaroslav@916: } jaroslav@343: String attr = "value"; jaroslav@916: setAttr(textArea, attr, getAttr(textArea, attr) + "\n" + newText); jaroslav@916: setAttr(textArea, "scrollTop", getAttr(textArea, "scrollHeight")); jaroslav@342: } jaroslav@323: jaroslav@916: private static void beginTest(Case c) { jaroslav@916: Object[] arr = new Object[2]; jaroslav@916: beginTest(c.getClassName() + "." + c.getMethodName(), c, arr); jaroslav@916: textArea = arr[0]; jaroslav@916: statusArea = arr[1]; jaroslav@916: } jaroslav@916: jaroslav@916: private static void finishTest(Case c, Object res) { jaroslav@916: if ("null".equals(res)) { jaroslav@922: setAttr(statusArea, "innerHTML", "Success"); jaroslav@916: } else { jaroslav@922: setAttr(statusArea, "innerHTML", "Result " + res); jaroslav@916: } jaroslav@916: statusArea = null; jaroslav@916: textArea = null; jaroslav@916: } jaroslav@916: jaroslav@916: @JavaScriptBody(args = { "test", "c", "arr" }, body = jaroslav@916: "var ul = window.document.getElementById('bck2brwsr.result');\n" jaroslav@916: + "var li = window.document.createElement('li');\n" jaroslav@922: + "var span = window.document.createElement('span');" jaroslav@916: + "span.innerHTML = test + ' - ';\n" jaroslav@922: + "var details = window.document.createElement('a');\n" jaroslav@922: + "details.innerHTML = 'Details';\n" jaroslav@922: + "details.href = '#';\n" jaroslav@916: + "var p = window.document.createElement('p');\n" jaroslav@916: + "var status = window.document.createElement('a');\n" jaroslav@916: + "status.innerHTML = 'running';" jaroslav@922: + "details.onclick = function() { li.appendChild(p); li.removeChild(details); status.innerHTML = 'Run Again'; status.href = '#'; };\n" jaroslav@916: + "status.onclick = function() { c.again__V_3Ljava_lang_Object_2(arr); }\n" jaroslav@916: + "var pre = window.document.createElement('textarea');\n" jaroslav@922: + "pre.cols = 100;" jaroslav@922: + "pre.rows = 10;" jaroslav@916: + "li.appendChild(span);\n" jaroslav@916: + "li.appendChild(status);\n" jaroslav@922: + "var span = window.document.createElement('span');" jaroslav@922: + "span.innerHTML = ' ';\n" jaroslav@922: + "li.appendChild(span);\n" jaroslav@922: + "li.appendChild(details);\n" jaroslav@916: + "p.appendChild(pre);\n" jaroslav@916: + "ul.appendChild(li);\n" jaroslav@916: + "arr[0] = pre;\n" jaroslav@916: + "arr[1] = status;\n" jaroslav@916: ) jaroslav@916: private static native void beginTest(String test, Case c, Object[] arr); jaroslav@916: jaroslav@519: @JavaScriptBody(args = { "url", "callback", "arr" }, body = "" jaroslav@519: + "var request = new XMLHttpRequest();\n" jaroslav@519: + "request.open('GET', url, true);\n" jaroslav@800: + "request.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');\n" jaroslav@519: + "request.onreadystatechange = function() {\n" jaroslav@519: + " if (this.readyState!==4) return;\n" jaroslav@519: + " arr[0] = this.responseText;\n" jaroslav@519: + " callback.run__V();\n" jaroslav@519: + "};" jaroslav@519: + "request.send();" jaroslav@519: ) jaroslav@519: private static native void loadText(String url, Runnable callback, String[] arr) throws IOException; jaroslav@332: jaroslav@1006: public void harness(String url) throws IOException { jaroslav@343: log("Connecting to " + url); jaroslav@519: Request r = new Request(url); jaroslav@519: } jaroslav@519: jaroslav@519: private static class Request implements Runnable { jaroslav@519: private final String[] arr = { null }; jaroslav@519: private final String url; jaroslav@939: private Case c; jaroslav@942: private int retries; jaroslav@519: jaroslav@519: private Request(String url) throws IOException { jaroslav@519: this.url = url; jaroslav@519: loadText(url, this, arr); jaroslav@519: } jaroslav@939: private Request(String url, String u) throws IOException { jaroslav@939: this.url = url; jaroslav@939: loadText(u, this, arr); jaroslav@939: } jaroslav@519: jaroslav@519: @Override jaroslav@519: public void run() { jaroslav@519: try { jaroslav@939: if (c == null) { jaroslav@939: String data = arr[0]; jaroslav@939: jaroslav@939: if (data == null) { jaroslav@939: log("Some error exiting"); jaroslav@939: closeWindow(); jaroslav@939: return; jaroslav@939: } jaroslav@939: jaroslav@939: if (data.isEmpty()) { jaroslav@939: log("No data, exiting"); jaroslav@939: closeWindow(); jaroslav@939: return; jaroslav@939: } jaroslav@939: jaroslav@939: c = Case.parseData(data); jaroslav@939: beginTest(c); jaroslav@940: log("Got \"" + data + "\""); jaroslav@940: } else { jaroslav@942: log("Processing \"" + arr[0] + "\" for " + retries + " time"); jaroslav@939: } jaroslav@942: Object result = retries++ >= 10 ? "java.lang.InterruptedException:timeout" : c.runTest(); jaroslav@939: finishTest(c, result); jaroslav@519: jaroslav@939: String u = url + "?request=" + c.getRequestId() + "&result=" + result; jaroslav@939: new Request(url, u); jaroslav@939: } catch (Exception ex) { jaroslav@939: if (ex instanceof InterruptedException) { jaroslav@940: log("Re-scheduling in 100ms"); jaroslav@940: schedule(this, 100); jaroslav@519: return; jaroslav@519: } jaroslav@707: log(ex.getClass().getName() + ":" + ex.getMessage()); jaroslav@342: } jaroslav@332: } jaroslav@332: } jaroslav@356: jaroslav@802: private static String encodeURL(String r) throws UnsupportedEncodingException { jaroslav@802: final String SPECIAL = "%$&+,/:;=?@"; jaroslav@381: StringBuilder sb = new StringBuilder(); jaroslav@802: byte[] utf8 = r.getBytes("UTF-8"); jaroslav@802: for (int i = 0; i < utf8.length; i++) { jaroslav@802: int ch = utf8[i] & 0xff; jaroslav@802: if (ch < 32 || ch > 127 || SPECIAL.indexOf(ch) >= 0) { jaroslav@802: final String numbers = "0" + Integer.toHexString(ch); jaroslav@802: sb.append("%").append(numbers.substring(numbers.length() - 2)); jaroslav@381: } else { jaroslav@381: if (ch == 32) { jaroslav@381: sb.append("+"); jaroslav@381: } else { jaroslav@381: sb.append((char)ch); jaroslav@381: } jaroslav@381: } jaroslav@381: } jaroslav@381: return sb.toString(); jaroslav@381: } jaroslav@381: jaroslav@939: static String invoke(String clazz, String method) throws jaroslav@939: ClassNotFoundException, InvocationTargetException, IllegalAccessException, jaroslav@939: InstantiationException, InterruptedException { jaroslav@939: final Object r = new Case(null).invokeMethod(clazz, method); jaroslav@356: return r == null ? "null" : r.toString().toString(); jaroslav@356: } jaroslav@323: jaroslav@360: /** Helper method that inspects the classpath and loads given resource jaroslav@360: * (usually a class file). Used while running tests in Rhino. jaroslav@360: * jaroslav@360: * @param name resource name to find jaroslav@360: * @return the array of bytes in the given resource jaroslav@360: * @throws IOException I/O in case something goes wrong jaroslav@360: */ jaroslav@360: public static byte[] read(String name) throws IOException { jaroslav@360: URL u = null; jaroslav@360: Enumeration en = Console.class.getClassLoader().getResources(name); jaroslav@360: while (en.hasMoreElements()) { jaroslav@360: u = en.nextElement(); jaroslav@360: } jaroslav@360: if (u == null) { jaroslav@360: throw new IOException("Can't find " + name); jaroslav@360: } jaroslav@360: try (InputStream is = u.openStream()) { jaroslav@360: byte[] arr; jaroslav@360: arr = new byte[is.available()]; jaroslav@360: int offset = 0; jaroslav@360: while (offset < arr.length) { jaroslav@360: int len = is.read(arr, offset, arr.length - offset); jaroslav@360: if (len == -1) { jaroslav@360: throw new IOException("Can't read " + name); jaroslav@360: } jaroslav@360: offset += len; jaroslav@360: } jaroslav@360: return arr; jaroslav@360: } jaroslav@360: } jaroslav@360: jaroslav@517: @JavaScriptBody(args = {}, body = "vm.desiredAssertionStatus = true;") jaroslav@517: private static void turnAssetionStatusOn() { jaroslav@517: } jaroslav@939: jaroslav@939: @JavaScriptBody(args = {"r", "time"}, body = jaroslav@940: "return window.setTimeout(function() { r.run__V(); }, time);") jaroslav@940: private static native Object schedule(Runnable r, int time); jaroslav@342: jaroslav@342: private static final class Case { jaroslav@342: private final Object data; jaroslav@939: private Object inst; jaroslav@342: jaroslav@342: private Case(Object data) { jaroslav@342: this.data = data; jaroslav@342: } jaroslav@342: jaroslav@342: public static Case parseData(String s) { jaroslav@342: return new Case(toJSON(s)); jaroslav@342: } jaroslav@342: jaroslav@342: public String getMethodName() { jaroslav@342: return value("methodName", data); jaroslav@342: } jaroslav@342: jaroslav@342: public String getClassName() { jaroslav@342: return value("className", data); jaroslav@342: } jaroslav@342: jaroslav@342: public String getRequestId() { jaroslav@342: return value("request", data); jaroslav@342: } jaroslav@526: jaroslav@526: public String getHtmlFragment() { jaroslav@526: return value("html", data); jaroslav@526: } jaroslav@342: jaroslav@916: void again(Object[] arr) { jaroslav@916: try { jaroslav@916: textArea = arr[0]; jaroslav@916: statusArea = arr[1]; jaroslav@916: setAttr(textArea, "value", ""); jaroslav@916: runTest(); jaroslav@916: } catch (Exception ex) { jaroslav@916: log(ex.getClass().getName() + ":" + ex.getMessage()); jaroslav@916: } jaroslav@916: } jaroslav@916: jaroslav@939: private Object runTest() throws IllegalAccessException, jaroslav@939: IllegalArgumentException, ClassNotFoundException, UnsupportedEncodingException, jaroslav@939: InvocationTargetException, InstantiationException, InterruptedException { jaroslav@916: if (this.getHtmlFragment() != null) { jaroslav@916: setAttr("bck2brwsr.fragment", "innerHTML", this.getHtmlFragment()); jaroslav@916: } jaroslav@916: log("Invoking " + this.getClassName() + '.' + this.getMethodName() + " as request: " + this.getRequestId()); jaroslav@916: Object result = invokeMethod(this.getClassName(), this.getMethodName()); jaroslav@916: setAttr("bck2brwsr.fragment", "innerHTML", ""); jaroslav@916: log("Result: " + result); jaroslav@916: result = encodeURL("" + result); jaroslav@916: log("Sending back: ...?request=" + this.getRequestId() + "&result=" + result); jaroslav@916: return result; jaroslav@916: } jaroslav@939: jaroslav@939: private Object invokeMethod(String clazz, String method) jaroslav@939: throws ClassNotFoundException, InvocationTargetException, jaroslav@939: InterruptedException, IllegalAccessException, IllegalArgumentException, jaroslav@939: InstantiationException { jaroslav@939: Method found = null; jaroslav@939: Class c = Class.forName(clazz); jaroslav@939: for (Method m : c.getMethods()) { jaroslav@939: if (m.getName().equals(method)) { jaroslav@939: found = m; jaroslav@939: } jaroslav@939: } jaroslav@939: Object res; jaroslav@939: if (found != null) { jaroslav@939: try { jaroslav@939: if ((found.getModifiers() & Modifier.STATIC) != 0) { jaroslav@939: res = found.invoke(null); jaroslav@939: } else { jaroslav@939: if (inst == null) { jaroslav@939: inst = c.newInstance(); jaroslav@939: } jaroslav@939: res = found.invoke(inst); jaroslav@939: } jaroslav@939: } catch (Throwable ex) { jaroslav@939: if (ex instanceof InvocationTargetException) { jaroslav@939: ex = ((InvocationTargetException) ex).getTargetException(); jaroslav@939: } jaroslav@939: if (ex instanceof InterruptedException) { jaroslav@939: throw (InterruptedException)ex; jaroslav@939: } jaroslav@939: res = ex.getClass().getName() + ":" + ex.getMessage(); jaroslav@939: } jaroslav@939: } else { jaroslav@939: res = "Can't find method " + method + " in " + clazz; jaroslav@939: } jaroslav@939: return res; jaroslav@939: } jaroslav@916: jaroslav@342: @JavaScriptBody(args = "s", body = "return eval('(' + s + ')');") jaroslav@342: private static native Object toJSON(String s); jaroslav@342: jaroslav@526: @JavaScriptBody(args = {"p", "d"}, body = jaroslav@526: "var v = d[p];\n" jaroslav@526: + "if (typeof v === 'undefined') return null;\n" jaroslav@526: + "return v.toString();" jaroslav@526: ) jaroslav@342: private static native String value(String p, Object d); jaroslav@342: } jaroslav@323: }