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@1041: package org.apidesign.bck2brwsr.launcher.fximpl; 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@1423: import net.java.html.js.JavaScriptBody; jaroslav@1041: import netscape.javascript.JSObject; jaroslav@323: jaroslav@323: /** jaroslav@323: * jaroslav@323: * @author Jaroslav Tulach jaroslav@323: */ jaroslav@1041: public final class Console { jaroslav@1041: public Console() { jaroslav@517: } jaroslav@323: jaroslav@1179: @JavaScriptBody(args = { "elem", "attr" }, body = "return elem[attr].toString();") jaroslav@1179: private static native Object getAttr(Object elem, String attr); jaroslav@1179: jaroslav@1179: @JavaScriptBody(args = { "id", "attr", "value" }, body = "window.document.getElementById(id)[attr] = value;") jaroslav@1179: private static native void setAttr(String id, String attr, Object value); jaroslav@1179: jaroslav@1179: @JavaScriptBody(args = { "elem", "attr", "value" }, body = "elem[attr] = value;") jaroslav@1179: private static native void setAttr(Object id, String attr, Object value); jaroslav@366: jaroslav@1041: private static 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@1423: Object[] arr = beginTest(c.getClassName() + "." + c.getMethodName(), c, new Object[2]); 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@1179: @JavaScriptBody(args = { "test", "c", "arr" }, body = jaroslav@1041: "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@1041: + "status.onclick = function() { c.again(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@1179: + "arr[1] = status;\n" jaroslav@1423: + "return arr;" jaroslav@1179: ) jaroslav@1423: private static native Object[] beginTest(String test, Case c, Object[] arr); jaroslav@916: jaroslav@1423: @JavaScriptBody(args = { "url", "callback" }, javacall = true, body = jaroslav@1041: "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@1179: + " try {\n" jaroslav@1423: + " callback.@org.apidesign.bck2brwsr.launcher.fximpl.OnMessage::onMessage(Ljava/lang/String;)(this.responseText);\n" jaroslav@1179: + " } catch (e) { alert(e); }\n" jaroslav@1179: + "};\n" jaroslav@1179: + "request.send();\n" jaroslav@1179: ) jaroslav@1423: private static native void loadText(String url, OnMessage callback) throws IOException; jaroslav@332: jaroslav@1041: public static void runHarness(String url) throws IOException { jaroslav@1041: new Console().harness(url); jaroslav@1041: } jaroslav@1041: jaroslav@1041: 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@1423: private static class Request implements Runnable, OnMessage { 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@1423: loadText(url, this); jaroslav@519: } jaroslav@939: private Request(String url, String u) throws IOException { jaroslav@939: this.url = url; jaroslav@1423: loadText(u, this); jaroslav@1423: } jaroslav@1423: jaroslav@1423: @Override jaroslav@1423: public void onMessage(String msg) { jaroslav@1423: arr[0] = msg; jaroslav@1423: run(); jaroslav@939: } jaroslav@519: jaroslav@519: @Override jaroslav@519: public void run() { jaroslav@1249: Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); 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@1752: Object result = retries++ >= 100 ? "java.lang.InterruptedException:timeout(" + retries + ")" : 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@1423: 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@1165: InputStream is = null; jaroslav@1165: try { jaroslav@1165: 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@1165: } finally { jaroslav@1165: if (is != null) is.close(); jaroslav@360: } jaroslav@360: } jaroslav@360: jaroslav@517: private static void turnAssetionStatusOn() { jaroslav@517: } jaroslav@939: jaroslav@1423: @JavaScriptBody(args = { "r", "time" }, javacall = true, body = jaroslav@1423: "return window.setTimeout(function() { " jaroslav@1423: + "r.@java.lang.Runnable::run()(); " jaroslav@1423: + "}, time);" jaroslav@1423: ) jaroslav@1179: 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@1041: return (String) value("methodName", data); jaroslav@342: } jaroslav@342: jaroslav@342: public String getClassName() { jaroslav@1041: return (String) value("className", data); jaroslav@342: } jaroslav@342: jaroslav@1041: public int getRequestId() { jaroslav@1041: Object v = value("request", data); jaroslav@1041: if (v instanceof Number) { jaroslav@1041: return ((Number)v).intValue(); jaroslav@1041: } jaroslav@1041: return Integer.parseInt(v.toString()); jaroslav@342: } jaroslav@526: jaroslav@526: public String getHtmlFragment() { jaroslav@1041: return (String) 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@1179: jaroslav@1179: @JavaScriptBody(args = { "s" }, body = "return eval('(' + s + ')');") jaroslav@1179: private static native Object toJSON(String s); jaroslav@342: jaroslav@1041: private static Object value(String p, Object d) { jaroslav@1041: return ((JSObject)d).getMember(p); jaroslav@1041: } jaroslav@342: } jaroslav@1041: jaroslav@1041: static { jaroslav@1041: turnAssetionStatusOn(); jaroslav@1041: } jaroslav@323: }