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@323: package org.apidesign.bck2brwsr.launcher; jaroslav@323: jaroslav@323: import java.awt.Desktop; jaroslav@331: import java.io.IOException; jaroslav@323: import java.io.InputStream; jaroslav@356: import java.io.InterruptedIOException; jaroslav@323: import java.io.OutputStream; jaroslav@323: import java.io.Writer; jaroslav@323: import java.net.URI; jaroslav@348: import java.net.URISyntaxException; jaroslav@323: import java.net.URL; jaroslav@348: import java.util.ArrayList; jaroslav@361: import java.util.Arrays; jaroslav@323: import java.util.Enumeration; jaroslav@348: import java.util.LinkedHashSet; jaroslav@348: import java.util.List; jaroslav@348: import java.util.Set; jaroslav@342: import java.util.concurrent.CountDownLatch; jaroslav@349: import java.util.concurrent.TimeUnit; jaroslav@362: import java.util.logging.Level; jaroslav@362: import java.util.logging.Logger; jaroslav@356: import javax.script.Invocable; jaroslav@356: import javax.script.ScriptEngine; jaroslav@356: import javax.script.ScriptEngineManager; jaroslav@356: import javax.script.ScriptException; jaroslav@331: import static org.apidesign.bck2brwsr.launcher.Bck2BrwsrLauncher.copyStream; jaroslav@323: import org.apidesign.vm4brwsr.Bck2Brwsr; jaroslav@323: import org.glassfish.grizzly.PortRange; jaroslav@323: import org.glassfish.grizzly.http.server.HttpHandler; jaroslav@323: import org.glassfish.grizzly.http.server.HttpServer; jaroslav@323: import org.glassfish.grizzly.http.server.NetworkListener; jaroslav@323: import org.glassfish.grizzly.http.server.Request; jaroslav@323: import org.glassfish.grizzly.http.server.Response; jaroslav@323: import org.glassfish.grizzly.http.server.ServerConfiguration; jaroslav@323: jaroslav@323: /** jaroslav@357: * Lightweight server to launch Bck2Brwsr applications and tests. jaroslav@357: * Supports execution in native browser as well as Java's internal jaroslav@357: * execution engine. jaroslav@323: */ jaroslav@323: public class Bck2BrwsrLauncher { jaroslav@362: private static final Logger LOG = Logger.getLogger(Bck2BrwsrLauncher.class.getName()); jaroslav@348: private Set loaders = new LinkedHashSet<>(); jaroslav@348: private List methods = new ArrayList<>(); jaroslav@349: private long timeOut; jaroslav@356: private String sen; jaroslav@357: private String showURL; jaroslav@357: private final Res resources = new Res(); jaroslav@348: jaroslav@348: jaroslav@348: public MethodInvocation addMethod(Class clazz, String method) { jaroslav@348: loaders.add(clazz.getClassLoader()); jaroslav@348: MethodInvocation c = new MethodInvocation(clazz.getName(), method); jaroslav@348: methods.add(c); jaroslav@348: return c; jaroslav@348: } jaroslav@348: jaroslav@349: public void setTimeout(long ms) { jaroslav@349: timeOut = ms; jaroslav@349: } jaroslav@348: jaroslav@356: public void setScriptEngineName(String sen) { jaroslav@356: this.sen = sen; jaroslav@356: } jaroslav@357: jaroslav@357: public void setStartPage(String startpage) { jaroslav@357: if (!startpage.startsWith("/")) { jaroslav@357: startpage = "/" + startpage; jaroslav@357: } jaroslav@357: this.showURL = startpage; jaroslav@357: } jaroslav@357: jaroslav@357: public void addClassLoader(ClassLoader url) { jaroslav@357: this.loaders.add(url); jaroslav@357: } jaroslav@356: jaroslav@323: public static void main( String[] args ) throws Exception { jaroslav@348: Bck2BrwsrLauncher l = new Bck2BrwsrLauncher(); jaroslav@357: l.setStartPage("org/apidesign/bck2brwsr/launcher/console.xhtml"); jaroslav@357: l.addClassLoader(Bck2BrwsrLauncher.class.getClassLoader()); jaroslav@348: l.execute(); jaroslav@357: System.in.read(); jaroslav@348: } jaroslav@348: jaroslav@348: jaroslav@356: public void execute() throws IOException { jaroslav@356: try { jaroslav@356: if (sen != null) { jaroslav@356: executeRhino(); jaroslav@357: } else if (showURL != null) { jaroslav@357: HttpServer server = initServer(); jaroslav@357: server.getServerConfiguration().addHttpHandler(new Page(resources, null), "/"); jaroslav@357: launchServerAndBrwsr(server, showURL); jaroslav@356: } else { jaroslav@356: executeInBrowser(); jaroslav@356: } jaroslav@356: } catch (InterruptedException ex) { jaroslav@356: final InterruptedIOException iio = new InterruptedIOException(ex.getMessage()); jaroslav@356: iio.initCause(ex); jaroslav@356: throw iio; jaroslav@356: } catch (Exception ex) { jaroslav@356: if (ex instanceof IOException) { jaroslav@356: throw (IOException)ex; jaroslav@356: } jaroslav@356: if (ex instanceof RuntimeException) { jaroslav@356: throw (RuntimeException)ex; jaroslav@356: } jaroslav@356: throw new IOException(ex); jaroslav@356: } jaroslav@356: } jaroslav@356: jaroslav@356: private void executeRhino() throws IOException, ScriptException, NoSuchMethodException { jaroslav@356: StringBuilder sb = new StringBuilder(); jaroslav@356: Bck2Brwsr.generate(sb, new Res()); jaroslav@356: jaroslav@356: ScriptEngineManager sem = new ScriptEngineManager(); jaroslav@356: ScriptEngine mach = sem.getEngineByExtension(sen); jaroslav@356: jaroslav@356: sb.append( jaroslav@360: "\nvar vm = new bck2brwsr(org.apidesign.bck2brwsr.launcher.Console.read);" jaroslav@356: + "\nfunction initVM() { return vm; };" jaroslav@356: + "\n"); jaroslav@356: jaroslav@356: Object res = mach.eval(sb.toString()); jaroslav@356: if (!(mach instanceof Invocable)) { jaroslav@356: throw new IOException("It is invocable object: " + res); jaroslav@356: } jaroslav@356: Invocable code = (Invocable) mach; jaroslav@356: jaroslav@356: Object vm = code.invokeFunction("initVM"); jaroslav@356: Object console = code.invokeMethod(vm, "loadClass", Console.class.getName()); jaroslav@356: jaroslav@356: final MethodInvocation[] cases = this.methods.toArray(new MethodInvocation[0]); jaroslav@356: for (MethodInvocation mi : cases) { jaroslav@356: mi.result = code.invokeMethod(console, jaroslav@356: "invoke__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2", jaroslav@356: mi.className, mi.methodName jaroslav@356: ).toString(); jaroslav@356: } jaroslav@356: } jaroslav@356: jaroslav@357: private HttpServer initServer() { jaroslav@323: HttpServer server = HttpServer.createSimpleServer(".", new PortRange(8080, 65535)); jaroslav@357: jaroslav@323: final ServerConfiguration conf = server.getServerConfiguration(); jaroslav@357: conf.addHttpHandler(new Page(resources, jaroslav@357: "org/apidesign/bck2brwsr/launcher/console.xhtml", jaroslav@332: "org.apidesign.bck2brwsr.launcher.Console", "welcome", "false" jaroslav@332: ), "/console"); jaroslav@348: conf.addHttpHandler(new VM(resources), "/bck2brwsr.js"); jaroslav@332: conf.addHttpHandler(new VMInit(), "/vm.js"); jaroslav@348: conf.addHttpHandler(new Classes(resources), "/classes/"); jaroslav@357: return server; jaroslav@357: } jaroslav@357: jaroslav@357: private void executeInBrowser() throws InterruptedException, URISyntaxException, IOException { jaroslav@357: final CountDownLatch wait = new CountDownLatch(1); jaroslav@357: final MethodInvocation[] cases = this.methods.toArray(new MethodInvocation[0]); jaroslav@357: jaroslav@357: HttpServer server = initServer(); jaroslav@357: ServerConfiguration conf = server.getServerConfiguration(); jaroslav@357: conf.addHttpHandler(new Page(resources, jaroslav@357: "org/apidesign/bck2brwsr/launcher/harness.xhtml" jaroslav@357: ), "/execute"); jaroslav@363: final int[] currentTest = { -1 }; jaroslav@332: conf.addHttpHandler(new HttpHandler() { jaroslav@332: int cnt; jaroslav@332: @Override jaroslav@332: public void service(Request request, Response response) throws Exception { jaroslav@342: String id = request.getParameter("request"); jaroslav@342: String value = request.getParameter("result"); jaroslav@342: if (id != null && value != null) { jaroslav@342: value = value.replace("%20", " "); jaroslav@342: cases[Integer.parseInt(id)].result = value; jaroslav@342: } jaroslav@363: currentTest[0] = cnt; jaroslav@342: jaroslav@342: if (cnt >= cases.length) { jaroslav@342: response.getWriter().write(""); jaroslav@342: wait.countDown(); jaroslav@342: cnt = 0; jaroslav@342: return; jaroslav@342: } jaroslav@342: jaroslav@332: response.getWriter().write("{" jaroslav@342: + "className: '" + cases[cnt].className + "', " jaroslav@342: + "methodName: '" + cases[cnt].methodName + "', " jaroslav@332: + "request: " + cnt jaroslav@332: + "}"); jaroslav@342: cnt++; jaroslav@332: } jaroslav@342: }, "/data"); jaroslav@357: jaroslav@357: launchServerAndBrwsr(server, "/execute"); jaroslav@323: jaroslav@363: for (;;) { jaroslav@363: int prev = currentTest[0]; jaroslav@363: if (wait.await(timeOut, TimeUnit.MILLISECONDS)) { jaroslav@363: break; jaroslav@363: } jaroslav@363: if (prev == currentTest[0]) { jaroslav@363: LOG.log( jaroslav@363: Level.WARNING, jaroslav@363: "Timeout and no test has been executed meanwhile (at {0}). Giving up.", jaroslav@363: currentTest[0] jaroslav@363: ); jaroslav@363: break; jaroslav@363: } jaroslav@363: LOG.log(Level.INFO, jaroslav@363: "Timeout, but tests got from {0} to {1}. Trying again.", jaroslav@363: new Object[]{prev, currentTest[0]} jaroslav@363: ); jaroslav@363: } jaroslav@349: server.stop(); jaroslav@323: } jaroslav@331: jaroslav@342: static void copyStream(InputStream is, OutputStream os, String baseURL, String... params) throws IOException { jaroslav@331: for (;;) { jaroslav@331: int ch = is.read(); jaroslav@331: if (ch == -1) { jaroslav@331: break; jaroslav@331: } jaroslav@331: if (ch == '$') { jaroslav@331: int cnt = is.read() - '0'; jaroslav@342: if (cnt == 'U' - '0') { jaroslav@342: os.write(baseURL.getBytes()); jaroslav@342: } jaroslav@331: if (cnt < params.length) { jaroslav@331: os.write(params[cnt].getBytes()); jaroslav@331: } jaroslav@331: } else { jaroslav@331: os.write(ch); jaroslav@331: } jaroslav@331: } jaroslav@331: } jaroslav@356: jaroslav@357: private void launchServerAndBrwsr(HttpServer server, final String page) throws IOException, URISyntaxException, InterruptedException { jaroslav@357: server.start(); jaroslav@357: NetworkListener listener = server.getListeners().iterator().next(); jaroslav@357: int port = listener.getPort(); jaroslav@357: jaroslav@357: URI uri = new URI("http://localhost:" + port + page); jaroslav@362: LOG.log(Level.INFO, "Showing {0}", uri); jaroslav@357: try { jaroslav@357: Desktop.getDesktop().browse(uri); jaroslav@357: } catch (UnsupportedOperationException ex) { jaroslav@357: String[] cmd = { jaroslav@357: "xdg-open", uri.toString() jaroslav@357: }; jaroslav@362: LOG.log(Level.INFO, "Launching {0}", Arrays.toString(cmd)); jaroslav@361: final Process process = Runtime.getRuntime().exec(cmd); jaroslav@361: InputStream stdout = process.getInputStream(); jaroslav@361: InputStream stderr = process.getErrorStream(); jaroslav@361: int res = process.waitFor(); jaroslav@362: LOG.log(Level.INFO, "Exit code: {0}", res); jaroslav@361: drain("StdOut", stdout); jaroslav@361: drain("StdErr", stderr); jaroslav@357: } jaroslav@361: } jaroslav@361: jaroslav@361: private static void drain(String name, InputStream is) throws IOException { jaroslav@361: int av = is.available(); jaroslav@361: if (av > 0) { jaroslav@362: StringBuilder sb = new StringBuilder(); jaroslav@362: sb.append("v== ").append(name).append(" ==v\n"); jaroslav@361: while (av-- > 0) { jaroslav@362: sb.append((char)is.read()); jaroslav@361: } jaroslav@362: sb.append("\n^== ").append(name).append(" ==^"); jaroslav@362: LOG.log(Level.INFO, sb.toString()); jaroslav@361: } jaroslav@357: } jaroslav@357: jaroslav@348: private class Res implements Bck2Brwsr.Resources { jaroslav@348: @Override jaroslav@348: public InputStream get(String resource) throws IOException { jaroslav@348: for (ClassLoader l : loaders) { jaroslav@348: URL u = null; jaroslav@348: Enumeration en = l.getResources(resource); jaroslav@348: while (en.hasMoreElements()) { jaroslav@348: u = en.nextElement(); jaroslav@348: } jaroslav@348: if (u != null) { jaroslav@348: return u.openStream(); jaroslav@348: } jaroslav@348: } jaroslav@348: throw new IOException("Can't find " + resource); jaroslav@348: } jaroslav@348: } jaroslav@330: jaroslav@332: private static class Page extends HttpHandler { jaroslav@332: private final String resource; jaroslav@331: private final String[] args; jaroslav@357: private final Res res; jaroslav@331: jaroslav@357: public Page(Res res, String resource, String... args) { jaroslav@357: this.res = res; jaroslav@332: this.resource = resource; jaroslav@331: this.args = args; jaroslav@330: } jaroslav@330: jaroslav@330: @Override jaroslav@330: public void service(Request request, Response response) throws Exception { jaroslav@357: String r = resource; jaroslav@357: if (r == null) { jaroslav@357: r = request.getHttpHandlerPath(); jaroslav@357: if (r.startsWith("/")) { jaroslav@357: r = r.substring(1); jaroslav@357: } jaroslav@357: } jaroslav@357: if (r.endsWith(".html") || r.endsWith(".xhtml")) { jaroslav@357: response.setContentType("text/html"); jaroslav@357: } jaroslav@330: OutputStream os = response.getOutputStream(); jaroslav@357: try (InputStream is = res.get(r)) { jaroslav@357: copyStream(is, os, request.getRequestURL().toString(), args); jaroslav@357: } catch (IOException ex) { jaroslav@357: response.setDetailMessage(ex.getLocalizedMessage()); jaroslav@357: response.setError(); jaroslav@357: response.setStatus(404); jaroslav@357: } jaroslav@330: } jaroslav@330: } jaroslav@330: jaroslav@330: private static class VM extends HttpHandler { jaroslav@348: private final Res loader; jaroslav@330: jaroslav@348: public VM(Res loader) { jaroslav@330: this.loader = loader; jaroslav@330: } jaroslav@330: jaroslav@330: @Override jaroslav@330: public void service(Request request, Response response) throws Exception { jaroslav@330: response.setCharacterEncoding("UTF-8"); jaroslav@330: response.setContentType("text/javascript"); jaroslav@330: Bck2Brwsr.generate(response.getWriter(), loader); jaroslav@330: } jaroslav@330: } jaroslav@332: private static class VMInit extends HttpHandler { jaroslav@332: public VMInit() { jaroslav@332: } jaroslav@332: jaroslav@332: @Override jaroslav@332: public void service(Request request, Response response) throws Exception { jaroslav@332: response.setCharacterEncoding("UTF-8"); jaroslav@332: response.setContentType("text/javascript"); jaroslav@332: response.getWriter().append( jaroslav@332: "function ldCls(res) {\n" jaroslav@332: + " var request = new XMLHttpRequest();\n" jaroslav@357: + " request.open('GET', '/classes/' + res, false);\n" jaroslav@332: + " request.send();\n" jaroslav@332: + " var arr = eval('(' + request.responseText + ')');\n" jaroslav@332: + " return arr;\n" jaroslav@332: + "}\n" jaroslav@332: + "var vm = new bck2brwsr(ldCls);\n"); jaroslav@332: } jaroslav@332: } jaroslav@330: jaroslav@330: private static class Classes extends HttpHandler { jaroslav@348: private final Res loader; jaroslav@330: jaroslav@348: public Classes(Res loader) { jaroslav@330: this.loader = loader; jaroslav@330: } jaroslav@330: jaroslav@330: @Override jaroslav@330: public void service(Request request, Response response) throws Exception { jaroslav@330: String res = request.getHttpHandlerPath(); jaroslav@330: if (res.startsWith("/")) { jaroslav@330: res = res.substring(1); jaroslav@330: } jaroslav@348: try (InputStream is = loader.get(res)) { jaroslav@348: response.setContentType("text/javascript"); jaroslav@348: Writer w = response.getWriter(); jaroslav@348: w.append("["); jaroslav@348: for (int i = 0;; i++) { jaroslav@348: int b = is.read(); jaroslav@348: if (b == -1) { jaroslav@348: break; jaroslav@348: } jaroslav@348: if (i > 0) { jaroslav@348: w.append(", "); jaroslav@348: } jaroslav@348: if (i % 20 == 0) { jaroslav@348: w.write("\n"); jaroslav@348: } jaroslav@348: if (b > 127) { jaroslav@348: b = b - 256; jaroslav@348: } jaroslav@348: w.append(Integer.toString(b)); jaroslav@348: } jaroslav@348: w.append("\n]"); jaroslav@348: } catch (IOException ex) { jaroslav@348: response.setError(); jaroslav@348: response.setDetailMessage(ex.getMessage()); jaroslav@330: } jaroslav@330: } jaroslav@330: } jaroslav@342: jaroslav@348: public static final class MethodInvocation { jaroslav@342: final String className; jaroslav@342: final String methodName; jaroslav@342: String result; jaroslav@342: jaroslav@348: MethodInvocation(String className, String methodName) { jaroslav@342: this.className = className; jaroslav@342: this.methodName = methodName; jaroslav@342: } jaroslav@348: jaroslav@348: @Override jaroslav@348: public String toString() { jaroslav@348: return result; jaroslav@348: } jaroslav@342: } jaroslav@323: }