launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Thu, 20 Dec 2012 21:54:33 +0100
branchlauncher
changeset 360 86f3ea771e24
parent 357 dc375a56fd15
child 361 98eb1066dab1
permissions -rw-r--r--
Moving the read method closer to the definition of the script
     1 /**
     2  * Back 2 Browser Bytecode Translator
     3  * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4  *
     5  * This program is free software: you can redistribute it and/or modify
     6  * it under the terms of the GNU General Public License as published by
     7  * the Free Software Foundation, version 2 of the License.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with this program. Look for COPYING file in the top folder.
    16  * If not, see http://opensource.org/licenses/GPL-2.0.
    17  */
    18 package org.apidesign.bck2brwsr.launcher;
    19 
    20 import java.awt.Desktop;
    21 import java.io.IOException;
    22 import java.io.InputStream;
    23 import java.io.InterruptedIOException;
    24 import java.io.OutputStream;
    25 import java.io.Writer;
    26 import java.net.URI;
    27 import java.net.URISyntaxException;
    28 import java.net.URL;
    29 import java.util.ArrayList;
    30 import java.util.Enumeration;
    31 import java.util.LinkedHashSet;
    32 import java.util.List;
    33 import java.util.Set;
    34 import java.util.concurrent.CountDownLatch;
    35 import java.util.concurrent.TimeUnit;
    36 import javax.script.Invocable;
    37 import javax.script.ScriptEngine;
    38 import javax.script.ScriptEngineManager;
    39 import javax.script.ScriptException;
    40 import static org.apidesign.bck2brwsr.launcher.Bck2BrwsrLauncher.copyStream;
    41 import org.apidesign.vm4brwsr.Bck2Brwsr;
    42 import org.glassfish.grizzly.PortRange;
    43 import org.glassfish.grizzly.http.server.HttpHandler;
    44 import org.glassfish.grizzly.http.server.HttpServer;
    45 import org.glassfish.grizzly.http.server.NetworkListener;
    46 import org.glassfish.grizzly.http.server.Request;
    47 import org.glassfish.grizzly.http.server.Response;
    48 import org.glassfish.grizzly.http.server.ServerConfiguration;
    49 
    50 /**
    51  * Lightweight server to launch Bck2Brwsr applications and tests.
    52  * Supports execution in native browser as well as Java's internal 
    53  * execution engine.
    54  */
    55 public class Bck2BrwsrLauncher {
    56     private Set<ClassLoader> loaders = new LinkedHashSet<>();
    57     private List<MethodInvocation> methods = new ArrayList<>();
    58     private long timeOut;
    59     private String sen;
    60     private String showURL;
    61     private final Res resources = new Res();
    62     
    63     
    64     public MethodInvocation addMethod(Class<?> clazz, String method) {
    65         loaders.add(clazz.getClassLoader());
    66         MethodInvocation c = new MethodInvocation(clazz.getName(), method);
    67         methods.add(c);
    68         return c;
    69     }
    70     
    71     public void setTimeout(long ms) {
    72         timeOut = ms;
    73     }
    74     
    75     public void setScriptEngineName(String sen) {
    76         this.sen = sen;
    77     }
    78 
    79     public void setStartPage(String startpage) {
    80         if (!startpage.startsWith("/")) {
    81             startpage = "/" + startpage;
    82         }
    83         this.showURL = startpage;
    84     }
    85 
    86     public void addClassLoader(ClassLoader url) {
    87         this.loaders.add(url);
    88     }
    89     
    90     public static void main( String[] args ) throws Exception {
    91         Bck2BrwsrLauncher l = new Bck2BrwsrLauncher();
    92         l.setStartPage("org/apidesign/bck2brwsr/launcher/console.xhtml");
    93         l.addClassLoader(Bck2BrwsrLauncher.class.getClassLoader());
    94         l.execute();
    95         System.in.read();
    96     }
    97 
    98 
    99     public void execute() throws IOException {
   100         try {
   101             if (sen != null) {
   102                 executeRhino();
   103             } else if (showURL != null) {
   104                 HttpServer server = initServer();
   105                 server.getServerConfiguration().addHttpHandler(new Page(resources, null), "/");
   106                 launchServerAndBrwsr(server, showURL);
   107             } else {
   108                 executeInBrowser();
   109             }
   110         } catch (InterruptedException ex) {
   111             final InterruptedIOException iio = new InterruptedIOException(ex.getMessage());
   112             iio.initCause(ex);
   113             throw iio;
   114         } catch (Exception ex) {
   115             if (ex instanceof IOException) {
   116                 throw (IOException)ex;
   117             }
   118             if (ex instanceof RuntimeException) {
   119                 throw (RuntimeException)ex;
   120             }
   121             throw new IOException(ex);
   122         }
   123     }
   124     
   125     private void executeRhino() throws IOException, ScriptException, NoSuchMethodException {
   126         StringBuilder sb = new StringBuilder();
   127         Bck2Brwsr.generate(sb, new Res());
   128 
   129         ScriptEngineManager sem = new ScriptEngineManager();
   130         ScriptEngine mach = sem.getEngineByExtension(sen);
   131 
   132         sb.append(
   133               "\nvar vm = new bck2brwsr(org.apidesign.bck2brwsr.launcher.Console.read);"
   134             + "\nfunction initVM() { return vm; };"
   135             + "\n");
   136 
   137         Object res = mach.eval(sb.toString());
   138         if (!(mach instanceof Invocable)) {
   139             throw new IOException("It is invocable object: " + res);
   140         }
   141         Invocable code = (Invocable) mach;
   142         
   143         Object vm = code.invokeFunction("initVM");
   144         Object console = code.invokeMethod(vm, "loadClass", Console.class.getName());
   145 
   146         final MethodInvocation[] cases = this.methods.toArray(new MethodInvocation[0]);
   147         for (MethodInvocation mi : cases) {
   148             mi.result = code.invokeMethod(console, 
   149                 "invoke__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2", 
   150                 mi.className, mi.methodName
   151             ).toString();
   152         }
   153     }
   154     
   155     private HttpServer initServer() {
   156         HttpServer server = HttpServer.createSimpleServer(".", new PortRange(8080, 65535));
   157 
   158         final ServerConfiguration conf = server.getServerConfiguration();
   159         conf.addHttpHandler(new Page(resources, 
   160             "org/apidesign/bck2brwsr/launcher/console.xhtml",
   161             "org.apidesign.bck2brwsr.launcher.Console", "welcome", "false"
   162         ), "/console");
   163         conf.addHttpHandler(new VM(resources), "/bck2brwsr.js");
   164         conf.addHttpHandler(new VMInit(), "/vm.js");
   165         conf.addHttpHandler(new Classes(resources), "/classes/");
   166         return server;
   167     }
   168     
   169     private void executeInBrowser() throws InterruptedException, URISyntaxException, IOException {
   170         final CountDownLatch wait = new CountDownLatch(1);
   171         final MethodInvocation[] cases = this.methods.toArray(new MethodInvocation[0]);
   172         
   173         HttpServer server = initServer();
   174         ServerConfiguration conf = server.getServerConfiguration();
   175         conf.addHttpHandler(new Page(resources, 
   176             "org/apidesign/bck2brwsr/launcher/harness.xhtml"
   177         ), "/execute");
   178         conf.addHttpHandler(new HttpHandler() {
   179             int cnt;
   180             @Override
   181             public void service(Request request, Response response) throws Exception {
   182                 String id = request.getParameter("request");
   183                 String value = request.getParameter("result");
   184                 if (id != null && value != null) {
   185                     value = value.replace("%20", " ");
   186                     cases[Integer.parseInt(id)].result = value;
   187                 }
   188                 
   189                 if (cnt >= cases.length) {
   190                     response.getWriter().write("");
   191                     wait.countDown();
   192                     cnt = 0;
   193                     return;
   194                 }
   195                 
   196                 response.getWriter().write("{"
   197                     + "className: '" + cases[cnt].className + "', "
   198                     + "methodName: '" + cases[cnt].methodName + "', "
   199                     + "request: " + cnt
   200                     + "}");
   201                 cnt++;
   202             }
   203         }, "/data");
   204 
   205         launchServerAndBrwsr(server, "/execute");
   206         
   207         wait.await(timeOut, TimeUnit.MILLISECONDS);
   208         server.stop();
   209     }
   210     
   211     static void copyStream(InputStream is, OutputStream os, String baseURL, String... params) throws IOException {
   212         for (;;) {
   213             int ch = is.read();
   214             if (ch == -1) {
   215                 break;
   216             }
   217             if (ch == '$') {
   218                 int cnt = is.read() - '0';
   219                 if (cnt == 'U' - '0') {
   220                     os.write(baseURL.getBytes());
   221                 }
   222                 if (cnt < params.length) {
   223                     os.write(params[cnt].getBytes());
   224                 }
   225             } else {
   226                 os.write(ch);
   227             }
   228         }
   229     }
   230 
   231     private void launchServerAndBrwsr(HttpServer server, final String page) throws IOException, URISyntaxException, InterruptedException {
   232         server.start();
   233         NetworkListener listener = server.getListeners().iterator().next();
   234         int port = listener.getPort();
   235         
   236         URI uri = new URI("http://localhost:" + port + page);
   237         try {
   238             Desktop.getDesktop().browse(uri);
   239         } catch (UnsupportedOperationException ex) {
   240             String[] cmd = { 
   241                 "xdg-open", uri.toString()
   242             };
   243             Runtime.getRuntime().exec(cmd).waitFor();
   244         }
   245         System.err.println("Showing " + uri);
   246     }
   247 
   248     private class Res implements Bck2Brwsr.Resources {
   249         @Override
   250         public InputStream get(String resource) throws IOException {
   251             for (ClassLoader l : loaders) {
   252                 URL u = null;
   253                 Enumeration<URL> en = l.getResources(resource);
   254                 while (en.hasMoreElements()) {
   255                     u = en.nextElement();
   256                 }
   257                 if (u != null) {
   258                     return u.openStream();
   259                 }
   260             }
   261             throw new IOException("Can't find " + resource);
   262         }
   263     }
   264 
   265     private static class Page extends HttpHandler {
   266         private final String resource;
   267         private final String[] args;
   268         private final Res res;
   269         
   270         public Page(Res res, String resource, String... args) {
   271             this.res = res;
   272             this.resource = resource;
   273             this.args = args;
   274         }
   275 
   276         @Override
   277         public void service(Request request, Response response) throws Exception {
   278             String r = resource;
   279             if (r == null) {
   280                 r = request.getHttpHandlerPath();
   281                 if (r.startsWith("/")) {
   282                     r = r.substring(1);
   283                 }
   284             }
   285             if (r.endsWith(".html") || r.endsWith(".xhtml")) {
   286                 response.setContentType("text/html");
   287             }
   288             OutputStream os = response.getOutputStream();
   289             try (InputStream is = res.get(r)) {
   290                 copyStream(is, os, request.getRequestURL().toString(), args);
   291             } catch (IOException ex) {
   292                 response.setDetailMessage(ex.getLocalizedMessage());
   293                 response.setError();
   294                 response.setStatus(404);
   295             }
   296         }
   297     }
   298 
   299     private static class VM extends HttpHandler {
   300         private final Res loader;
   301 
   302         public VM(Res loader) {
   303             this.loader = loader;
   304         }
   305 
   306         @Override
   307         public void service(Request request, Response response) throws Exception {
   308             response.setCharacterEncoding("UTF-8");
   309             response.setContentType("text/javascript");
   310             Bck2Brwsr.generate(response.getWriter(), loader);
   311         }
   312     }
   313     private static class VMInit extends HttpHandler {
   314         public VMInit() {
   315         }
   316 
   317         @Override
   318         public void service(Request request, Response response) throws Exception {
   319             response.setCharacterEncoding("UTF-8");
   320             response.setContentType("text/javascript");
   321             response.getWriter().append(
   322                 "function ldCls(res) {\n"
   323                 + "  var request = new XMLHttpRequest();\n"
   324                 + "  request.open('GET', '/classes/' + res, false);\n"
   325                 + "  request.send();\n"
   326                 + "  var arr = eval('(' + request.responseText + ')');\n"
   327                 + "  return arr;\n"
   328                 + "}\n"
   329                 + "var vm = new bck2brwsr(ldCls);\n");
   330         }
   331     }
   332 
   333     private static class Classes extends HttpHandler {
   334         private final Res loader;
   335 
   336         public Classes(Res loader) {
   337             this.loader = loader;
   338         }
   339 
   340         @Override
   341         public void service(Request request, Response response) throws Exception {
   342             String res = request.getHttpHandlerPath();
   343             if (res.startsWith("/")) {
   344                 res = res.substring(1);
   345             }
   346             try (InputStream is = loader.get(res)) {
   347                 response.setContentType("text/javascript");
   348                 Writer w = response.getWriter();
   349                 w.append("[");
   350                 for (int i = 0;; i++) {
   351                     int b = is.read();
   352                     if (b == -1) {
   353                         break;
   354                     }
   355                     if (i > 0) {
   356                         w.append(", ");
   357                     }
   358                     if (i % 20 == 0) {
   359                         w.write("\n");
   360                     }
   361                     if (b > 127) {
   362                         b = b - 256;
   363                     }
   364                     w.append(Integer.toString(b));
   365                 }
   366                 w.append("\n]");
   367             } catch (IOException ex) {
   368                 response.setError();
   369                 response.setDetailMessage(ex.getMessage());
   370             }
   371         }
   372     }
   373     
   374     public static final class MethodInvocation {
   375         final String className;
   376         final String methodName;
   377         String result;
   378 
   379         MethodInvocation(String className, String methodName) {
   380             this.className = className;
   381             this.methodName = methodName;
   382         }
   383 
   384         @Override
   385         public String toString() {
   386             return result;
   387         }
   388     }
   389 }