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@1041
|
18 |
package org.apidesign.bck2brwsr.launcher.fximpl;
|
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@1041
|
28 |
import netscape.javascript.JSObject;
|
jaroslav@1179
|
29 |
import org.apidesign.bck2brwsr.core.JavaScriptBody;
|
jaroslav@323
|
30 |
|
jaroslav@323
|
31 |
/**
|
jaroslav@323
|
32 |
*
|
jaroslav@323
|
33 |
* @author Jaroslav Tulach <jtulach@netbeans.org>
|
jaroslav@323
|
34 |
*/
|
jaroslav@1041
|
35 |
public final class Console {
|
jaroslav@1041
|
36 |
public Console() {
|
jaroslav@517
|
37 |
}
|
jaroslav@323
|
38 |
|
jaroslav@1179
|
39 |
@JavaScriptBody(args = { "elem", "attr" }, body = "return elem[attr].toString();")
|
jaroslav@1179
|
40 |
private static native Object getAttr(Object elem, String attr);
|
jaroslav@1179
|
41 |
|
jaroslav@1179
|
42 |
@JavaScriptBody(args = { "id", "attr", "value" }, body = "window.document.getElementById(id)[attr] = value;")
|
jaroslav@1179
|
43 |
private static native void setAttr(String id, String attr, Object value);
|
jaroslav@1179
|
44 |
|
jaroslav@1179
|
45 |
@JavaScriptBody(args = { "elem", "attr", "value" }, body = "elem[attr] = value;")
|
jaroslav@1179
|
46 |
private static native void setAttr(Object id, String attr, Object value);
|
jaroslav@366
|
47 |
|
jaroslav@1041
|
48 |
private static void closeWindow() {}
|
jaroslav@342
|
49 |
|
jaroslav@916
|
50 |
private static Object textArea;
|
jaroslav@916
|
51 |
private static Object statusArea;
|
jaroslav@916
|
52 |
|
jaroslav@343
|
53 |
private static void log(String newText) {
|
jaroslav@916
|
54 |
if (textArea == null) {
|
jaroslav@916
|
55 |
return;
|
jaroslav@916
|
56 |
}
|
jaroslav@343
|
57 |
String attr = "value";
|
jaroslav@916
|
58 |
setAttr(textArea, attr, getAttr(textArea, attr) + "\n" + newText);
|
jaroslav@916
|
59 |
setAttr(textArea, "scrollTop", getAttr(textArea, "scrollHeight"));
|
jaroslav@342
|
60 |
}
|
jaroslav@323
|
61 |
|
jaroslav@916
|
62 |
private static void beginTest(Case c) {
|
jaroslav@916
|
63 |
Object[] arr = new Object[2];
|
jaroslav@916
|
64 |
beginTest(c.getClassName() + "." + c.getMethodName(), c, arr);
|
jaroslav@916
|
65 |
textArea = arr[0];
|
jaroslav@916
|
66 |
statusArea = arr[1];
|
jaroslav@916
|
67 |
}
|
jaroslav@916
|
68 |
|
jaroslav@916
|
69 |
private static void finishTest(Case c, Object res) {
|
jaroslav@916
|
70 |
if ("null".equals(res)) {
|
jaroslav@922
|
71 |
setAttr(statusArea, "innerHTML", "Success");
|
jaroslav@916
|
72 |
} else {
|
jaroslav@922
|
73 |
setAttr(statusArea, "innerHTML", "Result " + res);
|
jaroslav@916
|
74 |
}
|
jaroslav@916
|
75 |
statusArea = null;
|
jaroslav@916
|
76 |
textArea = null;
|
jaroslav@916
|
77 |
}
|
jaroslav@916
|
78 |
|
jaroslav@1179
|
79 |
@JavaScriptBody(args = { "test", "c", "arr" }, body =
|
jaroslav@1041
|
80 |
"var ul = window.document.getElementById('bck2brwsr.result');\n"
|
jaroslav@916
|
81 |
+ "var li = window.document.createElement('li');\n"
|
jaroslav@922
|
82 |
+ "var span = window.document.createElement('span');"
|
jaroslav@916
|
83 |
+ "span.innerHTML = test + ' - ';\n"
|
jaroslav@922
|
84 |
+ "var details = window.document.createElement('a');\n"
|
jaroslav@922
|
85 |
+ "details.innerHTML = 'Details';\n"
|
jaroslav@922
|
86 |
+ "details.href = '#';\n"
|
jaroslav@916
|
87 |
+ "var p = window.document.createElement('p');\n"
|
jaroslav@916
|
88 |
+ "var status = window.document.createElement('a');\n"
|
jaroslav@916
|
89 |
+ "status.innerHTML = 'running';"
|
jaroslav@922
|
90 |
+ "details.onclick = function() { li.appendChild(p); li.removeChild(details); status.innerHTML = 'Run Again'; status.href = '#'; };\n"
|
jaroslav@1041
|
91 |
+ "status.onclick = function() { c.again(arr); }\n"
|
jaroslav@916
|
92 |
+ "var pre = window.document.createElement('textarea');\n"
|
jaroslav@922
|
93 |
+ "pre.cols = 100;"
|
jaroslav@922
|
94 |
+ "pre.rows = 10;"
|
jaroslav@916
|
95 |
+ "li.appendChild(span);\n"
|
jaroslav@916
|
96 |
+ "li.appendChild(status);\n"
|
jaroslav@922
|
97 |
+ "var span = window.document.createElement('span');"
|
jaroslav@922
|
98 |
+ "span.innerHTML = ' ';\n"
|
jaroslav@922
|
99 |
+ "li.appendChild(span);\n"
|
jaroslav@922
|
100 |
+ "li.appendChild(details);\n"
|
jaroslav@916
|
101 |
+ "p.appendChild(pre);\n"
|
jaroslav@916
|
102 |
+ "ul.appendChild(li);\n"
|
jaroslav@916
|
103 |
+ "arr[0] = pre;\n"
|
jaroslav@1179
|
104 |
+ "arr[1] = status;\n"
|
jaroslav@1179
|
105 |
)
|
jaroslav@1179
|
106 |
private static native void beginTest(String test, Case c, Object[] arr);
|
jaroslav@916
|
107 |
|
jaroslav@1179
|
108 |
@JavaScriptBody(args = { "url", "callback", "arr" }, body =
|
jaroslav@1041
|
109 |
"var request = new XMLHttpRequest();\n"
|
jaroslav@519
|
110 |
+ "request.open('GET', url, true);\n"
|
jaroslav@800
|
111 |
+ "request.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');\n"
|
jaroslav@519
|
112 |
+ "request.onreadystatechange = function() {\n"
|
jaroslav@519
|
113 |
+ " if (this.readyState!==4) return;\n"
|
jaroslav@1179
|
114 |
+ " try {\n"
|
jaroslav@519
|
115 |
+ " arr[0] = this.responseText;\n"
|
jaroslav@1179
|
116 |
+ " callback.run();\n"
|
jaroslav@1179
|
117 |
+ " } catch (e) { alert(e); }\n"
|
jaroslav@1179
|
118 |
+ "};\n"
|
jaroslav@1179
|
119 |
+ "request.send();\n"
|
jaroslav@1179
|
120 |
)
|
jaroslav@1179
|
121 |
private static native void loadText(String url, Runnable callback, String[] arr) throws IOException;
|
jaroslav@332
|
122 |
|
jaroslav@1041
|
123 |
public static void runHarness(String url) throws IOException {
|
jaroslav@1041
|
124 |
new Console().harness(url);
|
jaroslav@1041
|
125 |
}
|
jaroslav@1041
|
126 |
|
jaroslav@1041
|
127 |
public void harness(String url) throws IOException {
|
jaroslav@343
|
128 |
log("Connecting to " + url);
|
jaroslav@519
|
129 |
Request r = new Request(url);
|
jaroslav@519
|
130 |
}
|
jaroslav@519
|
131 |
|
jaroslav@519
|
132 |
private static class Request implements Runnable {
|
jaroslav@519
|
133 |
private final String[] arr = { null };
|
jaroslav@519
|
134 |
private final String url;
|
jaroslav@939
|
135 |
private Case c;
|
jaroslav@942
|
136 |
private int retries;
|
jaroslav@519
|
137 |
|
jaroslav@519
|
138 |
private Request(String url) throws IOException {
|
jaroslav@519
|
139 |
this.url = url;
|
jaroslav@1179
|
140 |
loadText(url, new Run(this), arr);
|
jaroslav@519
|
141 |
}
|
jaroslav@939
|
142 |
private Request(String url, String u) throws IOException {
|
jaroslav@939
|
143 |
this.url = url;
|
jaroslav@1179
|
144 |
loadText(u, new Run(this), arr);
|
jaroslav@939
|
145 |
}
|
jaroslav@519
|
146 |
|
jaroslav@519
|
147 |
@Override
|
jaroslav@519
|
148 |
public void run() {
|
jaroslav@519
|
149 |
try {
|
jaroslav@939
|
150 |
if (c == null) {
|
jaroslav@939
|
151 |
String data = arr[0];
|
jaroslav@939
|
152 |
|
jaroslav@939
|
153 |
if (data == null) {
|
jaroslav@939
|
154 |
log("Some error exiting");
|
jaroslav@939
|
155 |
closeWindow();
|
jaroslav@939
|
156 |
return;
|
jaroslav@939
|
157 |
}
|
jaroslav@939
|
158 |
|
jaroslav@939
|
159 |
if (data.isEmpty()) {
|
jaroslav@939
|
160 |
log("No data, exiting");
|
jaroslav@939
|
161 |
closeWindow();
|
jaroslav@939
|
162 |
return;
|
jaroslav@939
|
163 |
}
|
jaroslav@939
|
164 |
|
jaroslav@939
|
165 |
c = Case.parseData(data);
|
jaroslav@939
|
166 |
beginTest(c);
|
jaroslav@940
|
167 |
log("Got \"" + data + "\"");
|
jaroslav@940
|
168 |
} else {
|
jaroslav@942
|
169 |
log("Processing \"" + arr[0] + "\" for " + retries + " time");
|
jaroslav@939
|
170 |
}
|
jaroslav@942
|
171 |
Object result = retries++ >= 10 ? "java.lang.InterruptedException:timeout" : c.runTest();
|
jaroslav@939
|
172 |
finishTest(c, result);
|
jaroslav@519
|
173 |
|
jaroslav@939
|
174 |
String u = url + "?request=" + c.getRequestId() + "&result=" + result;
|
jaroslav@939
|
175 |
new Request(url, u);
|
jaroslav@939
|
176 |
} catch (Exception ex) {
|
jaroslav@939
|
177 |
if (ex instanceof InterruptedException) {
|
jaroslav@940
|
178 |
log("Re-scheduling in 100ms");
|
jaroslav@1179
|
179 |
schedule(new Run(this), 100);
|
jaroslav@519
|
180 |
return;
|
jaroslav@519
|
181 |
}
|
jaroslav@707
|
182 |
log(ex.getClass().getName() + ":" + ex.getMessage());
|
jaroslav@342
|
183 |
}
|
jaroslav@332
|
184 |
}
|
jaroslav@332
|
185 |
}
|
jaroslav@356
|
186 |
|
jaroslav@802
|
187 |
private static String encodeURL(String r) throws UnsupportedEncodingException {
|
jaroslav@802
|
188 |
final String SPECIAL = "%$&+,/:;=?@";
|
jaroslav@381
|
189 |
StringBuilder sb = new StringBuilder();
|
jaroslav@802
|
190 |
byte[] utf8 = r.getBytes("UTF-8");
|
jaroslav@802
|
191 |
for (int i = 0; i < utf8.length; i++) {
|
jaroslav@802
|
192 |
int ch = utf8[i] & 0xff;
|
jaroslav@802
|
193 |
if (ch < 32 || ch > 127 || SPECIAL.indexOf(ch) >= 0) {
|
jaroslav@802
|
194 |
final String numbers = "0" + Integer.toHexString(ch);
|
jaroslav@802
|
195 |
sb.append("%").append(numbers.substring(numbers.length() - 2));
|
jaroslav@381
|
196 |
} else {
|
jaroslav@381
|
197 |
if (ch == 32) {
|
jaroslav@381
|
198 |
sb.append("+");
|
jaroslav@381
|
199 |
} else {
|
jaroslav@381
|
200 |
sb.append((char)ch);
|
jaroslav@381
|
201 |
}
|
jaroslav@381
|
202 |
}
|
jaroslav@381
|
203 |
}
|
jaroslav@381
|
204 |
return sb.toString();
|
jaroslav@381
|
205 |
}
|
jaroslav@381
|
206 |
|
jaroslav@939
|
207 |
static String invoke(String clazz, String method) throws
|
jaroslav@939
|
208 |
ClassNotFoundException, InvocationTargetException, IllegalAccessException,
|
jaroslav@939
|
209 |
InstantiationException, InterruptedException {
|
jaroslav@939
|
210 |
final Object r = new Case(null).invokeMethod(clazz, method);
|
jaroslav@356
|
211 |
return r == null ? "null" : r.toString().toString();
|
jaroslav@356
|
212 |
}
|
jaroslav@323
|
213 |
|
jaroslav@360
|
214 |
/** Helper method that inspects the classpath and loads given resource
|
jaroslav@360
|
215 |
* (usually a class file). Used while running tests in Rhino.
|
jaroslav@360
|
216 |
*
|
jaroslav@360
|
217 |
* @param name resource name to find
|
jaroslav@360
|
218 |
* @return the array of bytes in the given resource
|
jaroslav@360
|
219 |
* @throws IOException I/O in case something goes wrong
|
jaroslav@360
|
220 |
*/
|
jaroslav@360
|
221 |
public static byte[] read(String name) throws IOException {
|
jaroslav@360
|
222 |
URL u = null;
|
jaroslav@360
|
223 |
Enumeration<URL> en = Console.class.getClassLoader().getResources(name);
|
jaroslav@360
|
224 |
while (en.hasMoreElements()) {
|
jaroslav@360
|
225 |
u = en.nextElement();
|
jaroslav@360
|
226 |
}
|
jaroslav@360
|
227 |
if (u == null) {
|
jaroslav@360
|
228 |
throw new IOException("Can't find " + name);
|
jaroslav@360
|
229 |
}
|
jaroslav@1165
|
230 |
InputStream is = null;
|
jaroslav@1165
|
231 |
try {
|
jaroslav@1165
|
232 |
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@1165
|
244 |
} finally {
|
jaroslav@1165
|
245 |
if (is != null) is.close();
|
jaroslav@360
|
246 |
}
|
jaroslav@360
|
247 |
}
|
jaroslav@360
|
248 |
|
jaroslav@517
|
249 |
private static void turnAssetionStatusOn() {
|
jaroslav@517
|
250 |
}
|
jaroslav@939
|
251 |
|
jaroslav@1179
|
252 |
@JavaScriptBody(args = { "r", "time" }, body = "return window.setTimeout(function() { r.run(); }, time);")
|
jaroslav@1179
|
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@1041
|
268 |
return (String) value("methodName", data);
|
jaroslav@342
|
269 |
}
|
jaroslav@342
|
270 |
|
jaroslav@342
|
271 |
public String getClassName() {
|
jaroslav@1041
|
272 |
return (String) value("className", data);
|
jaroslav@342
|
273 |
}
|
jaroslav@342
|
274 |
|
jaroslav@1041
|
275 |
public int getRequestId() {
|
jaroslav@1041
|
276 |
Object v = value("request", data);
|
jaroslav@1041
|
277 |
if (v instanceof Number) {
|
jaroslav@1041
|
278 |
return ((Number)v).intValue();
|
jaroslav@1041
|
279 |
}
|
jaroslav@1041
|
280 |
return Integer.parseInt(v.toString());
|
jaroslav@342
|
281 |
}
|
jaroslav@526
|
282 |
|
jaroslav@526
|
283 |
public String getHtmlFragment() {
|
jaroslav@1041
|
284 |
return (String) value("html", data);
|
jaroslav@526
|
285 |
}
|
jaroslav@342
|
286 |
|
jaroslav@916
|
287 |
void again(Object[] arr) {
|
jaroslav@916
|
288 |
try {
|
jaroslav@916
|
289 |
textArea = arr[0];
|
jaroslav@916
|
290 |
statusArea = arr[1];
|
jaroslav@916
|
291 |
setAttr(textArea, "value", "");
|
jaroslav@916
|
292 |
runTest();
|
jaroslav@916
|
293 |
} catch (Exception ex) {
|
jaroslav@916
|
294 |
log(ex.getClass().getName() + ":" + ex.getMessage());
|
jaroslav@916
|
295 |
}
|
jaroslav@916
|
296 |
}
|
jaroslav@916
|
297 |
|
jaroslav@939
|
298 |
private Object runTest() throws IllegalAccessException,
|
jaroslav@939
|
299 |
IllegalArgumentException, ClassNotFoundException, UnsupportedEncodingException,
|
jaroslav@939
|
300 |
InvocationTargetException, InstantiationException, InterruptedException {
|
jaroslav@916
|
301 |
if (this.getHtmlFragment() != null) {
|
jaroslav@916
|
302 |
setAttr("bck2brwsr.fragment", "innerHTML", this.getHtmlFragment());
|
jaroslav@916
|
303 |
}
|
jaroslav@916
|
304 |
log("Invoking " + this.getClassName() + '.' + this.getMethodName() + " as request: " + this.getRequestId());
|
jaroslav@916
|
305 |
Object result = invokeMethod(this.getClassName(), this.getMethodName());
|
jaroslav@916
|
306 |
setAttr("bck2brwsr.fragment", "innerHTML", "");
|
jaroslav@916
|
307 |
log("Result: " + result);
|
jaroslav@916
|
308 |
result = encodeURL("" + result);
|
jaroslav@916
|
309 |
log("Sending back: ...?request=" + this.getRequestId() + "&result=" + result);
|
jaroslav@916
|
310 |
return result;
|
jaroslav@916
|
311 |
}
|
jaroslav@939
|
312 |
|
jaroslav@939
|
313 |
private Object invokeMethod(String clazz, String method)
|
jaroslav@939
|
314 |
throws ClassNotFoundException, InvocationTargetException,
|
jaroslav@939
|
315 |
InterruptedException, IllegalAccessException, IllegalArgumentException,
|
jaroslav@939
|
316 |
InstantiationException {
|
jaroslav@939
|
317 |
Method found = null;
|
jaroslav@939
|
318 |
Class<?> c = Class.forName(clazz);
|
jaroslav@939
|
319 |
for (Method m : c.getMethods()) {
|
jaroslav@939
|
320 |
if (m.getName().equals(method)) {
|
jaroslav@939
|
321 |
found = m;
|
jaroslav@939
|
322 |
}
|
jaroslav@939
|
323 |
}
|
jaroslav@939
|
324 |
Object res;
|
jaroslav@939
|
325 |
if (found != null) {
|
jaroslav@939
|
326 |
try {
|
jaroslav@939
|
327 |
if ((found.getModifiers() & Modifier.STATIC) != 0) {
|
jaroslav@939
|
328 |
res = found.invoke(null);
|
jaroslav@939
|
329 |
} else {
|
jaroslav@939
|
330 |
if (inst == null) {
|
jaroslav@939
|
331 |
inst = c.newInstance();
|
jaroslav@939
|
332 |
}
|
jaroslav@939
|
333 |
res = found.invoke(inst);
|
jaroslav@939
|
334 |
}
|
jaroslav@939
|
335 |
} catch (Throwable ex) {
|
jaroslav@939
|
336 |
if (ex instanceof InvocationTargetException) {
|
jaroslav@939
|
337 |
ex = ((InvocationTargetException) ex).getTargetException();
|
jaroslav@939
|
338 |
}
|
jaroslav@939
|
339 |
if (ex instanceof InterruptedException) {
|
jaroslav@939
|
340 |
throw (InterruptedException)ex;
|
jaroslav@939
|
341 |
}
|
jaroslav@939
|
342 |
res = ex.getClass().getName() + ":" + ex.getMessage();
|
jaroslav@939
|
343 |
}
|
jaroslav@939
|
344 |
} else {
|
jaroslav@939
|
345 |
res = "Can't find method " + method + " in " + clazz;
|
jaroslav@939
|
346 |
}
|
jaroslav@939
|
347 |
return res;
|
jaroslav@939
|
348 |
}
|
jaroslav@1179
|
349 |
|
jaroslav@1179
|
350 |
@JavaScriptBody(args = { "s" }, body = "return eval('(' + s + ')');")
|
jaroslav@1179
|
351 |
private static native Object toJSON(String s);
|
jaroslav@342
|
352 |
|
jaroslav@1041
|
353 |
private static Object value(String p, Object d) {
|
jaroslav@1041
|
354 |
return ((JSObject)d).getMember(p);
|
jaroslav@1041
|
355 |
}
|
jaroslav@342
|
356 |
}
|
jaroslav@1041
|
357 |
|
jaroslav@1041
|
358 |
static {
|
jaroslav@1041
|
359 |
turnAssetionStatusOn();
|
jaroslav@1041
|
360 |
}
|
jaroslav@323
|
361 |
}
|