rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Wed, 17 Apr 2013 17:04:40 +0200
branchfx
changeset 1004 04efef2a9c1e
parent 969 df08556c5c7c
permissions -rw-r--r--
Rather than piggybacking on first alert call, use the fact that the server and FX Web View are in the same VM and notify the view that bck2brwsr.js is about to be served from the server.
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@323
    18
package org.apidesign.bck2brwsr.launcher;
jaroslav@323
    19
jaroslav@382
    20
import java.io.Closeable;
jaroslav@365
    21
import java.io.File;
jaroslav@331
    22
import java.io.IOException;
jaroslav@323
    23
import java.io.InputStream;
jaroslav@356
    24
import java.io.InterruptedIOException;
jaroslav@323
    25
import java.io.OutputStream;
jaroslav@800
    26
import java.io.UnsupportedEncodingException;
jaroslav@323
    27
import java.io.Writer;
jaroslav@323
    28
import java.net.URI;
jaroslav@348
    29
import java.net.URISyntaxException;
jaroslav@323
    30
import java.net.URL;
jaroslav@348
    31
import java.util.ArrayList;
jaroslav@361
    32
import java.util.Arrays;
jaroslav@323
    33
import java.util.Enumeration;
jaroslav@348
    34
import java.util.LinkedHashSet;
jaroslav@348
    35
import java.util.List;
jaroslav@348
    36
import java.util.Set;
jaroslav@371
    37
import java.util.concurrent.BlockingQueue;
jaroslav@342
    38
import java.util.concurrent.CountDownLatch;
jaroslav@371
    39
import java.util.concurrent.LinkedBlockingQueue;
jaroslav@349
    40
import java.util.concurrent.TimeUnit;
jaroslav@362
    41
import java.util.logging.Level;
jaroslav@362
    42
import java.util.logging.Logger;
jaroslav@667
    43
import org.apidesign.bck2brwsr.launcher.InvocationContext.Resource;
jaroslav@323
    44
import org.apidesign.vm4brwsr.Bck2Brwsr;
jaroslav@323
    45
import org.glassfish.grizzly.PortRange;
jaroslav@323
    46
import org.glassfish.grizzly.http.server.HttpHandler;
jaroslav@323
    47
import org.glassfish.grizzly.http.server.HttpServer;
jaroslav@323
    48
import org.glassfish.grizzly.http.server.NetworkListener;
jaroslav@323
    49
import org.glassfish.grizzly.http.server.Request;
jaroslav@323
    50
import org.glassfish.grizzly.http.server.Response;
jaroslav@323
    51
import org.glassfish.grizzly.http.server.ServerConfiguration;
jaroslav@745
    52
import org.glassfish.grizzly.http.util.HttpStatus;
jaroslav@323
    53
jaroslav@323
    54
/**
jaroslav@357
    55
 * Lightweight server to launch Bck2Brwsr applications and tests.
jaroslav@357
    56
 * Supports execution in native browser as well as Java's internal 
jaroslav@357
    57
 * execution engine.
jaroslav@323
    58
 */
jaroslav@844
    59
class Bck2BrwsrLauncher extends Launcher implements Closeable {
jaroslav@362
    60
    private static final Logger LOG = Logger.getLogger(Bck2BrwsrLauncher.class.getName());
jaroslav@622
    61
    private static final InvocationContext END = new InvocationContext(null, null, null);
jaroslav@622
    62
    private final Set<ClassLoader> loaders = new LinkedHashSet<>();
jaroslav@622
    63
    private final BlockingQueue<InvocationContext> methods = new LinkedBlockingQueue<>();
jaroslav@349
    64
    private long timeOut;
jaroslav@357
    65
    private final Res resources = new Res();
jaroslav@382
    66
    private final String cmd;
jaroslav@371
    67
    private Object[] brwsr;
jaroslav@371
    68
    private HttpServer server;
jaroslav@371
    69
    private CountDownLatch wait;
jaroslav@613
    70
    
jaroslav@382
    71
    public Bck2BrwsrLauncher(String cmd) {
jaroslav@382
    72
        this.cmd = cmd;
jaroslav@382
    73
    }
jaroslav@348
    74
    
jaroslav@382
    75
    @Override
jaroslav@622
    76
    InvocationContext runMethod(InvocationContext c) throws IOException {
jaroslav@622
    77
        loaders.add(c.clazz.getClassLoader());
jaroslav@348
    78
        methods.add(c);
jaroslav@371
    79
        try {
jaroslav@371
    80
            c.await(timeOut);
jaroslav@371
    81
        } catch (InterruptedException ex) {
jaroslav@371
    82
            throw new IOException(ex);
jaroslav@371
    83
        }
jaroslav@348
    84
        return c;
jaroslav@348
    85
    }
jaroslav@348
    86
    
jaroslav@349
    87
    public void setTimeout(long ms) {
jaroslav@349
    88
        timeOut = ms;
jaroslav@349
    89
    }
jaroslav@348
    90
    
jaroslav@357
    91
    public void addClassLoader(ClassLoader url) {
jaroslav@357
    92
        this.loaders.add(url);
jaroslav@357
    93
    }
jaroslav@845
    94
    
jaroslav@845
    95
    ClassLoader[] loaders() {
jaroslav@845
    96
        return loaders.toArray(new ClassLoader[loaders.size()]);
jaroslav@845
    97
    }
jaroslav@348
    98
jaroslav@382
    99
    public void showURL(String startpage) throws IOException {
jaroslav@371
   100
        if (!startpage.startsWith("/")) {
jaroslav@371
   101
            startpage = "/" + startpage;
jaroslav@371
   102
        }
jaroslav@613
   103
        HttpServer s = initServer(".", true);
jaroslav@742
   104
        int last = startpage.lastIndexOf('/');
jaroslav@924
   105
        String prefix = startpage.substring(0, last);
jaroslav@742
   106
        String simpleName = startpage.substring(last);
jaroslav@924
   107
        s.getServerConfiguration().addHttpHandler(new SubTree(resources, prefix), "/");
jaroslav@382
   108
        try {
jaroslav@742
   109
            launchServerAndBrwsr(s, simpleName);
jaroslav@382
   110
        } catch (URISyntaxException | InterruptedException ex) {
jaroslav@382
   111
            throw new IOException(ex);
jaroslav@382
   112
        }
jaroslav@371
   113
    }
jaroslav@348
   114
jaroslav@613
   115
    void showDirectory(File dir, String startpage) throws IOException {
jaroslav@613
   116
        if (!startpage.startsWith("/")) {
jaroslav@613
   117
            startpage = "/" + startpage;
jaroslav@613
   118
        }
jaroslav@613
   119
        HttpServer s = initServer(dir.getPath(), false);
jaroslav@613
   120
        try {
jaroslav@613
   121
            launchServerAndBrwsr(s, startpage);
jaroslav@613
   122
        } catch (URISyntaxException | InterruptedException ex) {
jaroslav@613
   123
            throw new IOException(ex);
jaroslav@613
   124
        }
jaroslav@613
   125
    }
jaroslav@613
   126
jaroslav@382
   127
    @Override
jaroslav@371
   128
    public void initialize() throws IOException {
jaroslav@356
   129
        try {
jaroslav@371
   130
            executeInBrowser();
jaroslav@356
   131
        } catch (InterruptedException ex) {
jaroslav@356
   132
            final InterruptedIOException iio = new InterruptedIOException(ex.getMessage());
jaroslav@356
   133
            iio.initCause(ex);
jaroslav@356
   134
            throw iio;
jaroslav@356
   135
        } catch (Exception ex) {
jaroslav@356
   136
            if (ex instanceof IOException) {
jaroslav@356
   137
                throw (IOException)ex;
jaroslav@356
   138
            }
jaroslav@356
   139
            if (ex instanceof RuntimeException) {
jaroslav@356
   140
                throw (RuntimeException)ex;
jaroslav@356
   141
            }
jaroslav@356
   142
            throw new IOException(ex);
jaroslav@356
   143
        }
jaroslav@356
   144
    }
jaroslav@356
   145
    
jaroslav@613
   146
    private HttpServer initServer(String path, boolean addClasses) throws IOException {
jaroslav@613
   147
        HttpServer s = HttpServer.createSimpleServer(path, new PortRange(8080, 65535));
jaroslav@357
   148
jaroslav@371
   149
        final ServerConfiguration conf = s.getServerConfiguration();
jaroslav@613
   150
        if (addClasses) {
jaroslav@1004
   151
            conf.addHttpHandler(new VM(), "/bck2brwsr.js");
jaroslav@613
   152
            conf.addHttpHandler(new Classes(resources), "/classes/");
jaroslav@613
   153
        }
jaroslav@371
   154
        return s;
jaroslav@357
   155
    }
jaroslav@357
   156
    
jaroslav@357
   157
    private void executeInBrowser() throws InterruptedException, URISyntaxException, IOException {
jaroslav@371
   158
        wait = new CountDownLatch(1);
jaroslav@613
   159
        server = initServer(".", true);
jaroslav@623
   160
        final ServerConfiguration conf = server.getServerConfiguration();
jaroslav@623
   161
        
jaroslav@623
   162
        class DynamicResourceHandler extends HttpHandler {
jaroslav@623
   163
            private final InvocationContext ic;
jaroslav@623
   164
            public DynamicResourceHandler(InvocationContext ic) {
jaroslav@667
   165
                if (ic == null || ic.resources.isEmpty()) {
jaroslav@623
   166
                    throw new NullPointerException();
jaroslav@623
   167
                }
jaroslav@623
   168
                this.ic = ic;
jaroslav@667
   169
                for (Resource r : ic.resources) {
jaroslav@667
   170
                    conf.addHttpHandler(this, r.httpPath);
jaroslav@667
   171
                }
jaroslav@623
   172
            }
jaroslav@623
   173
jaroslav@623
   174
            public void close() {
jaroslav@623
   175
                conf.removeHttpHandler(this);
jaroslav@623
   176
            }
jaroslav@623
   177
            
jaroslav@623
   178
            @Override
jaroslav@623
   179
            public void service(Request request, Response response) throws Exception {
jaroslav@667
   180
                for (Resource r : ic.resources) {
jaroslav@667
   181
                    if (r.httpPath.equals(request.getRequestURI())) {
jaroslav@667
   182
                        LOG.log(Level.INFO, "Serving HttpResource for {0}", request.getRequestURI());
jaroslav@667
   183
                        response.setContentType(r.httpType);
jaroslav@946
   184
                        r.httpContent.reset();
jaroslav@954
   185
                        String[] params = null;
jaroslav@954
   186
                        if (r.parameters.length != 0) {
jaroslav@954
   187
                            params = new String[r.parameters.length];
jaroslav@954
   188
                            for (int i = 0; i < r.parameters.length; i++) {
jaroslav@954
   189
                                params[i] = request.getParameter(r.parameters[i]);
jaroslav@954
   190
                            }
jaroslav@954
   191
                        }
jaroslav@954
   192
                        
jaroslav@954
   193
                        copyStream(r.httpContent, response.getOutputStream(), null, params);
jaroslav@667
   194
                    }
jaroslav@623
   195
                }
jaroslav@623
   196
            }
jaroslav@623
   197
        }
jaroslav@623
   198
        
jaroslav@357
   199
        conf.addHttpHandler(new Page(resources, 
jaroslav@357
   200
            "org/apidesign/bck2brwsr/launcher/harness.xhtml"
jaroslav@357
   201
        ), "/execute");
jaroslav@623
   202
        
jaroslav@332
   203
        conf.addHttpHandler(new HttpHandler() {
jaroslav@332
   204
            int cnt;
jaroslav@622
   205
            List<InvocationContext> cases = new ArrayList<>();
jaroslav@623
   206
            DynamicResourceHandler prev;
jaroslav@332
   207
            @Override
jaroslav@332
   208
            public void service(Request request, Response response) throws Exception {
jaroslav@342
   209
                String id = request.getParameter("request");
jaroslav@342
   210
                String value = request.getParameter("result");
jaroslav@800
   211
                if (value != null && value.indexOf((char)0xC5) != -1) {
jaroslav@800
   212
                    value = toUTF8(value);
jaroslav@800
   213
                }
jaroslav@364
   214
                
jaroslav@709
   215
                
jaroslav@709
   216
                InvocationContext mi = null;
jaroslav@709
   217
                int caseNmbr = -1;
jaroslav@709
   218
                
jaroslav@342
   219
                if (id != null && value != null) {
jaroslav@364
   220
                    LOG.log(Level.INFO, "Received result for case {0} = {1}", new Object[]{id, value});
jaroslav@381
   221
                    value = decodeURL(value);
jaroslav@709
   222
                    int indx = Integer.parseInt(id);
jaroslav@709
   223
                    cases.get(indx).result(value, null);
jaroslav@709
   224
                    if (++indx < cases.size()) {
jaroslav@709
   225
                        mi = cases.get(indx);
jaroslav@709
   226
                        LOG.log(Level.INFO, "Re-executing case {0}", indx);
jaroslav@709
   227
                        caseNmbr = indx;
jaroslav@709
   228
                    }
jaroslav@709
   229
                } else {
jaroslav@709
   230
                    if (!cases.isEmpty()) {
jaroslav@709
   231
                        LOG.info("Re-executing test cases");
jaroslav@709
   232
                        mi = cases.get(0);
jaroslav@709
   233
                        caseNmbr = 0;
jaroslav@709
   234
                    }
jaroslav@342
   235
                }
jaroslav@342
   236
                
jaroslav@623
   237
                if (prev != null) {
jaroslav@623
   238
                    prev.close();
jaroslav@623
   239
                    prev = null;
jaroslav@623
   240
                }
jaroslav@623
   241
                
jaroslav@709
   242
                if (mi == null) {
jaroslav@709
   243
                    mi = methods.take();
jaroslav@709
   244
                    caseNmbr = cnt++;
jaroslav@709
   245
                }
jaroslav@372
   246
                if (mi == END) {
jaroslav@342
   247
                    response.getWriter().write("");
jaroslav@342
   248
                    wait.countDown();
jaroslav@342
   249
                    cnt = 0;
jaroslav@372
   250
                    LOG.log(Level.INFO, "End of data reached. Exiting.");
jaroslav@342
   251
                    return;
jaroslav@342
   252
                }
jaroslav@342
   253
                
jaroslav@667
   254
                if (!mi.resources.isEmpty()) {
jaroslav@623
   255
                    prev = new DynamicResourceHandler(mi);
jaroslav@623
   256
                }
jaroslav@623
   257
                
jaroslav@371
   258
                cases.add(mi);
jaroslav@622
   259
                final String cn = mi.clazz.getName();
jaroslav@371
   260
                final String mn = mi.methodName;
jaroslav@709
   261
                LOG.log(Level.INFO, "Request for {0} case. Sending {1}.{2}", new Object[]{caseNmbr, cn, mn});
jaroslav@332
   262
                response.getWriter().write("{"
jaroslav@364
   263
                    + "className: '" + cn + "', "
jaroslav@364
   264
                    + "methodName: '" + mn + "', "
jaroslav@709
   265
                    + "request: " + caseNmbr
jaroslav@526
   266
                );
jaroslav@526
   267
                if (mi.html != null) {
jaroslav@526
   268
                    response.getWriter().write(", html: '");
jaroslav@526
   269
                    response.getWriter().write(encodeJSON(mi.html));
jaroslav@526
   270
                    response.getWriter().write("'");
jaroslav@526
   271
                }
jaroslav@526
   272
                response.getWriter().write("}");
jaroslav@332
   273
            }
jaroslav@342
   274
        }, "/data");
jaroslav@357
   275
jaroslav@371
   276
        this.brwsr = launchServerAndBrwsr(server, "/execute");
jaroslav@371
   277
    }
jaroslav@371
   278
    
jaroslav@526
   279
    private static String encodeJSON(String in) {
jaroslav@526
   280
        StringBuilder sb = new StringBuilder();
jaroslav@526
   281
        for (int i = 0; i < in.length(); i++) {
jaroslav@526
   282
            char ch = in.charAt(i);
jaroslav@526
   283
            if (ch < 32 || ch == '\'' || ch == '"') {
jaroslav@526
   284
                sb.append("\\u");
jaroslav@526
   285
                String hs = "0000" + Integer.toHexString(ch);
jaroslav@526
   286
                hs = hs.substring(hs.length() - 4);
jaroslav@526
   287
                sb.append(hs);
jaroslav@526
   288
            } else {
jaroslav@526
   289
                sb.append(ch);
jaroslav@526
   290
            }
jaroslav@526
   291
        }
jaroslav@526
   292
        return sb.toString();
jaroslav@526
   293
    }
jaroslav@526
   294
    
jaroslav@382
   295
    @Override
jaroslav@382
   296
    public void shutdown() throws IOException {
jaroslav@372
   297
        methods.offer(END);
jaroslav@363
   298
        for (;;) {
jaroslav@371
   299
            int prev = methods.size();
jaroslav@382
   300
            try {
jaroslav@389
   301
                if (wait != null && wait.await(timeOut, TimeUnit.MILLISECONDS)) {
jaroslav@382
   302
                    break;
jaroslav@382
   303
                }
jaroslav@382
   304
            } catch (InterruptedException ex) {
jaroslav@382
   305
                throw new IOException(ex);
jaroslav@363
   306
            }
jaroslav@371
   307
            if (prev == methods.size()) {
jaroslav@363
   308
                LOG.log(
jaroslav@363
   309
                    Level.WARNING, 
jaroslav@363
   310
                    "Timeout and no test has been executed meanwhile (at {0}). Giving up.", 
jaroslav@371
   311
                    methods.size()
jaroslav@363
   312
                );
jaroslav@363
   313
                break;
jaroslav@363
   314
            }
jaroslav@363
   315
            LOG.log(Level.INFO, 
jaroslav@363
   316
                "Timeout, but tests got from {0} to {1}. Trying again.", 
jaroslav@371
   317
                new Object[]{prev, methods.size()}
jaroslav@363
   318
            );
jaroslav@363
   319
        }
jaroslav@365
   320
        stopServerAndBrwsr(server, brwsr);
jaroslav@323
   321
    }
jaroslav@331
   322
    
jaroslav@342
   323
    static void copyStream(InputStream is, OutputStream os, String baseURL, String... params) throws IOException {
jaroslav@331
   324
        for (;;) {
jaroslav@331
   325
            int ch = is.read();
jaroslav@331
   326
            if (ch == -1) {
jaroslav@331
   327
                break;
jaroslav@331
   328
            }
jaroslav@523
   329
            if (ch == '$' && params.length > 0) {
jaroslav@331
   330
                int cnt = is.read() - '0';
jaroslav@954
   331
                if (baseURL != null && cnt == 'U' - '0') {
jaroslav@714
   332
                    os.write(baseURL.getBytes("UTF-8"));
jaroslav@762
   333
                } else {
jaroslav@762
   334
                    if (cnt >= 0 && cnt < params.length) {
jaroslav@762
   335
                        os.write(params[cnt].getBytes("UTF-8"));
jaroslav@762
   336
                    } else {
jaroslav@762
   337
                        os.write('$');
jaroslav@762
   338
                        os.write(cnt + '0');
jaroslav@762
   339
                    }
jaroslav@331
   340
                }
jaroslav@331
   341
            } else {
jaroslav@331
   342
                os.write(ch);
jaroslav@331
   343
            }
jaroslav@331
   344
        }
jaroslav@331
   345
    }
jaroslav@356
   346
jaroslav@365
   347
    private Object[] launchServerAndBrwsr(HttpServer server, final String page) throws IOException, URISyntaxException, InterruptedException {
jaroslav@357
   348
        server.start();
jaroslav@357
   349
        NetworkListener listener = server.getListeners().iterator().next();
jaroslav@357
   350
        int port = listener.getPort();
jaroslav@357
   351
        
jaroslav@357
   352
        URI uri = new URI("http://localhost:" + port + page);
jaroslav@844
   353
        return showBrwsr(uri);
jaroslav@361
   354
    }
jaroslav@800
   355
    private static String toUTF8(String value) throws UnsupportedEncodingException {
jaroslav@800
   356
        byte[] arr = new byte[value.length()];
jaroslav@800
   357
        for (int i = 0; i < arr.length; i++) {
jaroslav@800
   358
            arr[i] = (byte)value.charAt(i);
jaroslav@800
   359
        }
jaroslav@800
   360
        return new String(arr, "UTF-8");
jaroslav@800
   361
    }
jaroslav@361
   362
    
jaroslav@381
   363
    private static String decodeURL(String s) {
jaroslav@381
   364
        for (;;) {
jaroslav@381
   365
            int pos = s.indexOf('%');
jaroslav@381
   366
            if (pos == -1) {
jaroslav@381
   367
                return s;
jaroslav@381
   368
            }
jaroslav@381
   369
            int i = Integer.parseInt(s.substring(pos + 1, pos + 2), 16);
jaroslav@381
   370
            s = s.substring(0, pos) + (char)i + s.substring(pos + 2);
jaroslav@381
   371
        }
jaroslav@381
   372
    }
jaroslav@381
   373
    
jaroslav@382
   374
    private void stopServerAndBrwsr(HttpServer server, Object[] brwsr) throws IOException {
jaroslav@389
   375
        if (brwsr == null) {
jaroslav@389
   376
            return;
jaroslav@389
   377
        }
jaroslav@365
   378
        Process process = (Process)brwsr[0];
jaroslav@365
   379
        
jaroslav@365
   380
        server.stop();
jaroslav@365
   381
        InputStream stdout = process.getInputStream();
jaroslav@365
   382
        InputStream stderr = process.getErrorStream();
jaroslav@365
   383
        drain("StdOut", stdout);
jaroslav@365
   384
        drain("StdErr", stderr);
jaroslav@365
   385
        process.destroy();
jaroslav@382
   386
        int res;
jaroslav@382
   387
        try {
jaroslav@382
   388
            res = process.waitFor();
jaroslav@382
   389
        } catch (InterruptedException ex) {
jaroslav@382
   390
            throw new IOException(ex);
jaroslav@382
   391
        }
jaroslav@365
   392
        LOG.log(Level.INFO, "Exit code: {0}", res);
jaroslav@365
   393
jaroslav@365
   394
        deleteTree((File)brwsr[1]);
jaroslav@365
   395
    }
jaroslav@365
   396
    
jaroslav@361
   397
    private static void drain(String name, InputStream is) throws IOException {
jaroslav@361
   398
        int av = is.available();
jaroslav@361
   399
        if (av > 0) {
jaroslav@362
   400
            StringBuilder sb = new StringBuilder();
jaroslav@362
   401
            sb.append("v== ").append(name).append(" ==v\n");
jaroslav@361
   402
            while (av-- > 0) {
jaroslav@362
   403
                sb.append((char)is.read());
jaroslav@361
   404
            }
jaroslav@362
   405
            sb.append("\n^== ").append(name).append(" ==^");
jaroslav@362
   406
            LOG.log(Level.INFO, sb.toString());
jaroslav@361
   407
        }
jaroslav@357
   408
    }
jaroslav@357
   409
jaroslav@365
   410
    private void deleteTree(File file) {
jaroslav@369
   411
        if (file == null) {
jaroslav@369
   412
            return;
jaroslav@369
   413
        }
jaroslav@365
   414
        File[] arr = file.listFiles();
jaroslav@365
   415
        if (arr != null) {
jaroslav@365
   416
            for (File s : arr) {
jaroslav@365
   417
                deleteTree(s);
jaroslav@365
   418
            }
jaroslav@365
   419
        }
jaroslav@365
   420
        file.delete();
jaroslav@365
   421
    }
jaroslav@365
   422
jaroslav@382
   423
    @Override
jaroslav@382
   424
    public void close() throws IOException {
jaroslav@382
   425
        shutdown();
jaroslav@382
   426
    }
jaroslav@382
   427
jaroslav@844
   428
    protected Object[] showBrwsr(URI uri) throws IOException {
jaroslav@844
   429
        LOG.log(Level.INFO, "Showing {0}", uri);
jaroslav@844
   430
        if (cmd == null) {
jaroslav@844
   431
            try {
jaroslav@844
   432
                LOG.log(Level.INFO, "Trying Desktop.browse on {0} {2} by {1}", new Object[] {
jaroslav@844
   433
                    System.getProperty("java.vm.name"),
jaroslav@844
   434
                    System.getProperty("java.vm.vendor"),
jaroslav@844
   435
                    System.getProperty("java.vm.version"),
jaroslav@844
   436
                });
jaroslav@844
   437
                java.awt.Desktop.getDesktop().browse(uri);
jaroslav@844
   438
                LOG.log(Level.INFO, "Desktop.browse successfully finished");
jaroslav@844
   439
                return null;
jaroslav@844
   440
            } catch (UnsupportedOperationException ex) {
jaroslav@844
   441
                LOG.log(Level.INFO, "Desktop.browse not supported: {0}", ex.getMessage());
jaroslav@844
   442
                LOG.log(Level.FINE, null, ex);
jaroslav@844
   443
            }
jaroslav@844
   444
        }
jaroslav@844
   445
        {
jaroslav@844
   446
            String cmdName = cmd == null ? "xdg-open" : cmd;
jaroslav@844
   447
            String[] cmdArr = { 
jaroslav@844
   448
                cmdName, uri.toString()
jaroslav@844
   449
            };
jaroslav@844
   450
            LOG.log(Level.INFO, "Launching {0}", Arrays.toString(cmdArr));
jaroslav@844
   451
            final Process process = Runtime.getRuntime().exec(cmdArr);
jaroslav@844
   452
            return new Object[] { process, null };
jaroslav@844
   453
        }
jaroslav@844
   454
    }
jaroslav@844
   455
jaroslav@845
   456
    void generateBck2BrwsrJS(StringBuilder sb, Bck2Brwsr.Resources loader) throws IOException {
jaroslav@845
   457
        Bck2Brwsr.generate(sb, loader);
jaroslav@845
   458
        sb.append(
jaroslav@845
   459
            "(function WrapperVM(global) {"
jaroslav@845
   460
            + "  function ldCls(res) {\n"
jaroslav@845
   461
            + "    var request = new XMLHttpRequest();\n"
jaroslav@845
   462
            + "    request.open('GET', '/classes/' + res, false);\n"
jaroslav@845
   463
            + "    request.send();\n"
jaroslav@845
   464
            + "    if (request.status !== 200) return null;\n"
jaroslav@845
   465
            + "    var arr = eval('(' + request.responseText + ')');\n"
jaroslav@845
   466
            + "    return arr;\n"
jaroslav@845
   467
            + "  }\n"
jaroslav@845
   468
            + "  var prevvm = global.bck2brwsr;\n"
jaroslav@845
   469
            + "  global.bck2brwsr = function() {\n"
jaroslav@845
   470
            + "    var args = Array.prototype.slice.apply(arguments);\n"
jaroslav@845
   471
            + "    args.unshift(ldCls);\n"
jaroslav@845
   472
            + "    return prevvm.apply(null, args);\n"
jaroslav@845
   473
            + "  };\n"
jaroslav@845
   474
            + "})(this);\n");
jaroslav@845
   475
    }
jaroslav@845
   476
jaroslav@348
   477
    private class Res implements Bck2Brwsr.Resources {
jaroslav@348
   478
        @Override
jaroslav@348
   479
        public InputStream get(String resource) throws IOException {
jaroslav@348
   480
            for (ClassLoader l : loaders) {
jaroslav@348
   481
                URL u = null;
jaroslav@348
   482
                Enumeration<URL> en = l.getResources(resource);
jaroslav@348
   483
                while (en.hasMoreElements()) {
jaroslav@348
   484
                    u = en.nextElement();
jaroslav@348
   485
                }
jaroslav@348
   486
                if (u != null) {
jaroslav@348
   487
                    return u.openStream();
jaroslav@348
   488
                }
jaroslav@348
   489
            }
jaroslav@348
   490
            throw new IOException("Can't find " + resource);
jaroslav@348
   491
        }
jaroslav@348
   492
    }
jaroslav@330
   493
jaroslav@332
   494
    private static class Page extends HttpHandler {
jaroslav@924
   495
        final String resource;
jaroslav@331
   496
        private final String[] args;
jaroslav@357
   497
        private final Res res;
jaroslav@331
   498
        
jaroslav@357
   499
        public Page(Res res, String resource, String... args) {
jaroslav@357
   500
            this.res = res;
jaroslav@332
   501
            this.resource = resource;
jaroslav@526
   502
            this.args = args.length == 0 ? new String[] { "$0" } : args;
jaroslav@330
   503
        }
jaroslav@330
   504
jaroslav@330
   505
        @Override
jaroslav@330
   506
        public void service(Request request, Response response) throws Exception {
jaroslav@924
   507
            String r = computePage(request);
jaroslav@741
   508
            if (r.startsWith("/")) {
jaroslav@741
   509
                r = r.substring(1);
jaroslav@357
   510
            }
jaroslav@523
   511
            String[] replace = {};
jaroslav@433
   512
            if (r.endsWith(".html")) {
jaroslav@357
   513
                response.setContentType("text/html");
jaroslav@433
   514
                LOG.info("Content type text/html");
jaroslav@523
   515
                replace = args;
jaroslav@433
   516
            }
jaroslav@433
   517
            if (r.endsWith(".xhtml")) {
jaroslav@433
   518
                response.setContentType("application/xhtml+xml");
jaroslav@433
   519
                LOG.info("Content type application/xhtml+xml");
jaroslav@523
   520
                replace = args;
jaroslav@357
   521
            }
jaroslav@330
   522
            OutputStream os = response.getOutputStream();
jaroslav@357
   523
            try (InputStream is = res.get(r)) {
jaroslav@523
   524
                copyStream(is, os, request.getRequestURL().toString(), replace);
jaroslav@357
   525
            } catch (IOException ex) {
jaroslav@357
   526
                response.setDetailMessage(ex.getLocalizedMessage());
jaroslav@357
   527
                response.setError();
jaroslav@357
   528
                response.setStatus(404);
jaroslav@357
   529
            }
jaroslav@330
   530
        }
jaroslav@924
   531
jaroslav@924
   532
        protected String computePage(Request request) {
jaroslav@924
   533
            String r = resource;
jaroslav@924
   534
            if (r == null) {
jaroslav@924
   535
                r = request.getHttpHandlerPath();
jaroslav@924
   536
            }
jaroslav@924
   537
            return r;
jaroslav@924
   538
        }
jaroslav@924
   539
    }
jaroslav@924
   540
    
jaroslav@924
   541
    private static class SubTree extends Page {
jaroslav@924
   542
jaroslav@924
   543
        public SubTree(Res res, String resource, String... args) {
jaroslav@924
   544
            super(res, resource, args);
jaroslav@924
   545
        }
jaroslav@924
   546
jaroslav@924
   547
        @Override
jaroslav@924
   548
        protected String computePage(Request request) {
jaroslav@924
   549
            return resource + request.getHttpHandlerPath();
jaroslav@924
   550
        }
jaroslav@924
   551
        
jaroslav@924
   552
        
jaroslav@330
   553
    }
jaroslav@330
   554
jaroslav@1004
   555
    private class VM extends HttpHandler {
jaroslav@330
   556
        @Override
jaroslav@330
   557
        public void service(Request request, Response response) throws Exception {
jaroslav@330
   558
            response.setCharacterEncoding("UTF-8");
jaroslav@330
   559
            response.setContentType("text/javascript");
jaroslav@1004
   560
            StringBuilder sb = new StringBuilder();
jaroslav@1004
   561
            generateBck2BrwsrJS(sb, Bck2BrwsrLauncher.this.resources);
jaroslav@1004
   562
            response.getWriter().write(sb.toString());
jaroslav@330
   563
        }
jaroslav@330
   564
    }
jaroslav@330
   565
jaroslav@330
   566
    private static class Classes extends HttpHandler {
jaroslav@348
   567
        private final Res loader;
jaroslav@330
   568
jaroslav@348
   569
        public Classes(Res loader) {
jaroslav@330
   570
            this.loader = loader;
jaroslav@330
   571
        }
jaroslav@330
   572
jaroslav@330
   573
        @Override
jaroslav@330
   574
        public void service(Request request, Response response) throws Exception {
jaroslav@330
   575
            String res = request.getHttpHandlerPath();
jaroslav@330
   576
            if (res.startsWith("/")) {
jaroslav@330
   577
                res = res.substring(1);
jaroslav@330
   578
            }
jaroslav@348
   579
            try (InputStream is = loader.get(res)) {
jaroslav@348
   580
                response.setContentType("text/javascript");
jaroslav@348
   581
                Writer w = response.getWriter();
jaroslav@348
   582
                w.append("[");
jaroslav@348
   583
                for (int i = 0;; i++) {
jaroslav@348
   584
                    int b = is.read();
jaroslav@348
   585
                    if (b == -1) {
jaroslav@348
   586
                        break;
jaroslav@348
   587
                    }
jaroslav@348
   588
                    if (i > 0) {
jaroslav@348
   589
                        w.append(", ");
jaroslav@348
   590
                    }
jaroslav@348
   591
                    if (i % 20 == 0) {
jaroslav@348
   592
                        w.write("\n");
jaroslav@348
   593
                    }
jaroslav@348
   594
                    if (b > 127) {
jaroslav@348
   595
                        b = b - 256;
jaroslav@348
   596
                    }
jaroslav@348
   597
                    w.append(Integer.toString(b));
jaroslav@348
   598
                }
jaroslav@348
   599
                w.append("\n]");
jaroslav@348
   600
            } catch (IOException ex) {
jaroslav@745
   601
                response.setStatus(HttpStatus.NOT_FOUND_404);
jaroslav@348
   602
                response.setError();
jaroslav@348
   603
                response.setDetailMessage(ex.getMessage());
jaroslav@330
   604
            }
jaroslav@330
   605
        }
jaroslav@330
   606
    }
jaroslav@323
   607
}