launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/JVMBridge.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Mon, 22 Dec 2014 20:57:23 +0100
changeset 1755 03e8d4077e55
parent 1676 87f66a77adf9
child 1787 ea12a3bb4b33
permissions -rw-r--r--
Supporting Fn.KeepAlive
     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.fximpl;
    19 
    20 import java.io.BufferedReader;
    21 import java.io.Reader;
    22 import java.lang.ref.WeakReference;
    23 import java.net.URL;
    24 import java.util.ArrayList;
    25 import java.util.Arrays;
    26 import java.util.Collection;
    27 import java.util.List;
    28 import java.util.TooManyListenersException;
    29 import java.util.concurrent.Executor;
    30 import java.util.logging.Level;
    31 import java.util.logging.Logger;
    32 import javafx.application.Platform;
    33 import javafx.beans.value.ChangeListener;
    34 import javafx.scene.web.WebEngine;
    35 import netscape.javascript.JSObject;
    36 import org.netbeans.html.boot.impl.FindResources;
    37 import org.netbeans.html.boot.impl.FnUtils;
    38 import org.netbeans.html.boot.spi.Fn;
    39 
    40 /**
    41  *
    42  * @author Jaroslav Tulach <jtulach@netbeans.org>
    43  */
    44 public final class JVMBridge {
    45     static final Logger LOG = Logger.getLogger(JVMBridge.class.getName());
    46     
    47     private final WebEngine engine;
    48     private final ClassLoader cl;
    49     private final WebPresenter presenter;
    50     
    51     private static ClassLoader[] ldrs;
    52     private static ChangeListener<Void> onBck2BrwsrLoad;
    53     
    54     JVMBridge(WebEngine eng) {
    55         this.engine = eng;
    56         final ClassLoader p = JVMBridge.class.getClassLoader().getParent();
    57         this.presenter = new WebPresenter();
    58         this.cl = FnUtils.newLoader(presenter, presenter, p);
    59     }
    60         
    61     public static void registerClassLoaders(ClassLoader[] loaders) {
    62         ldrs = loaders.clone();
    63     }
    64     
    65     public static void addBck2BrwsrLoad(ChangeListener<Void> l) throws TooManyListenersException {
    66         if (onBck2BrwsrLoad != null) {
    67             throw new TooManyListenersException();
    68         }
    69         onBck2BrwsrLoad = l;
    70     }
    71 
    72     public static void onBck2BrwsrLoad() {
    73         ChangeListener<Void> l = onBck2BrwsrLoad;
    74         if (l != null) {
    75             l.changed(null, null, null);
    76         }
    77     }
    78     
    79     public Class<?> loadClass(String name) throws ClassNotFoundException {
    80         Fn.activate(presenter);
    81         return Class.forName(name, true, cl);
    82     }
    83     
    84     private final class WebPresenter implements Fn.Presenter,
    85     FindResources, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Fn.KeepAlive {
    86         @Override
    87         public void findResources(String name, Collection<? super URL> results, boolean oneIsEnough) {
    88             if (ldrs != null) for (ClassLoader l : ldrs) {
    89                 URL u = l.getResource(name);
    90                 if (u != null) {
    91                     results.add(u);
    92                 }
    93             }
    94         }
    95 
    96         @Override
    97         public Fn defineFn(String code, String... names) {
    98             return defineJSFn(code, names, null);
    99         }
   100         
   101         @Override
   102         public Fn defineFn(String code, String[] names, boolean[] keepAlive) {
   103             return defineJSFn(code, names, keepAlive);
   104         }
   105         
   106         private JSFn defineJSFn(String code, String[] names, boolean[] keepAlive) {
   107             StringBuilder sb = new StringBuilder();
   108             sb.append("(function() {");
   109             sb.append("  return function(");
   110             String sep = "";
   111             if (names != null) for (String n : names) {
   112                 sb.append(sep).append(n);
   113                 sep = ",";
   114             }
   115             sb.append(") {\n");
   116             sb.append(code);
   117             sb.append("};");
   118             sb.append("})()");
   119             
   120             JSObject x = (JSObject) engine.executeScript(sb.toString());
   121             return new JSFn(this, x, keepAlive);
   122         }
   123 
   124         @Override
   125         public void displayPage(URL page, Runnable onPageLoad) {
   126             throw new UnsupportedOperationException("Not supported yet.");
   127         }
   128 
   129         @Override
   130         public void loadScript(Reader code) throws Exception {
   131             BufferedReader r = new BufferedReader(code);
   132             StringBuilder sb = new StringBuilder();
   133             for (;;) {
   134                 String l = r.readLine();
   135                 if (l == null) {
   136                     break;
   137                 }
   138                 sb.append(l).append('\n');
   139             }
   140             engine.executeScript(sb.toString());
   141         }
   142 
   143         @Override
   144         public Object toJava(Object js) {
   145             if (js instanceof Weak) {
   146                 js = ((Weak)js).get();
   147             }
   148             return checkArray(js);
   149         }
   150 
   151         @Override
   152         public Object toJavaScript(Object toReturn) {
   153             if (toReturn instanceof Object[]) {
   154                 return convertArrays((Object[]) toReturn);
   155             }
   156             return toReturn;
   157         }
   158 
   159         @Override
   160         public void execute(Runnable command) {
   161             if (Platform.isFxApplicationThread()) {
   162                 command.run();
   163             } else {
   164                 Platform.runLater(command);
   165             }
   166         }
   167         
   168         final JSObject convertArrays(Object[] arr) {
   169             for (int i = 0; i < arr.length; i++) {
   170                 if (arr[i] instanceof Object[]) {
   171                     arr[i] = convertArrays((Object[]) arr[i]);
   172                 }
   173             }
   174             final JSObject wrapArr = (JSObject) wrapArrFn().call("array", arr); // NOI18N
   175             return wrapArr;
   176         }
   177 
   178         private JSObject wrapArrImpl;
   179 
   180         private final JSObject wrapArrFn() {
   181             if (wrapArrImpl == null) {
   182                 try {
   183                     wrapArrImpl = (JSObject) defineJSFn("  var k = {};"
   184                         + "  k.array= function() {"
   185                         + "    return Array.prototype.slice.call(arguments);"
   186                         + "  };"
   187                         + "  return k;", null, null
   188                     ).invokeImpl(null, false);
   189                 } catch (Exception ex) {
   190                     throw new IllegalStateException(ex);
   191                 }
   192             }
   193             return wrapArrImpl;
   194         }
   195 
   196         final Object checkArray(Object val) {
   197             int length = ((Number) arraySizeFn().call("array", val, null)).intValue();
   198             if (length == -1) {
   199                 return val;
   200             }
   201             Object[] arr = new Object[length];
   202             arraySizeFn().call("array", val, arr);
   203             return arr;
   204         }
   205         private JSObject arraySize;
   206 
   207         private final JSObject arraySizeFn() {
   208             if (arraySize == null) {
   209                 try {
   210                     arraySize = (JSObject) defineJSFn("  var k = {};"
   211                         + "  k.array = function(arr, to) {"
   212                         + "    if (to === null) {"
   213                         + "      if (Object.prototype.toString.call(arr) === '[object Array]') return arr.length;"
   214                         + "      else return -1;"
   215                         + "    } else {"
   216                         + "      var l = arr.length;"
   217                         + "      for (var i = 0; i < l; i++) to[i] = arr[i];"
   218                         + "      return l;"
   219                         + "    }"
   220                         + "  };"
   221                         + "  return k;", null, null
   222                     ).invokeImpl(null, false);
   223                 } catch (Exception ex) {
   224                     throw new IllegalStateException(ex);
   225                 }
   226             }
   227             return arraySize;
   228         }
   229 
   230     }
   231     
   232     private static final class JSFn extends Fn {
   233         private final JSObject fn;
   234         private final boolean[] keepAlive;
   235 
   236         private JSFn(WebPresenter cl, JSObject fn, boolean[] keepAlive) {
   237             super(cl);
   238             this.fn = fn;
   239             this.keepAlive = keepAlive;
   240         }
   241 
   242         @Override
   243         public Object invoke(Object thiz, Object... args) throws Exception {
   244             return invokeImpl(thiz, true, args);
   245         }
   246 
   247         final Object invokeImpl(Object thiz, boolean arrayChecks, Object... args) throws Exception {
   248             try {
   249                 List<Object> all = new ArrayList<Object>(args.length + 1);
   250                 all.add(thiz == null ? fn : thiz);
   251                 for (int i = 0; i < args.length; i++) {
   252                     Object conv = args[i];
   253                     if (arrayChecks) {
   254                         if (args[i] instanceof Object[]) {
   255                             Object[] arr = (Object[]) args[i];
   256                             conv = ((WebPresenter) presenter()).convertArrays(arr);
   257                         }
   258                         if (conv != null && keepAlive != null
   259                             && !keepAlive[i] && !isJSReady(conv)
   260                             && !conv.getClass().getSimpleName().equals("$JsCallbacks$") // NOI18N
   261                             ) {
   262                             conv = new Weak(conv);
   263                         }
   264                     }
   265                     all.add(conv);
   266                 }
   267                 Object ret = fn.call("call", all.toArray()); // NOI18N
   268                 if (ret instanceof Weak) {
   269                     ret = ((Weak) ret).get();
   270                 }
   271                 if (ret == fn) {
   272                     return null;
   273                 }
   274                 if (!arrayChecks) {
   275                     return ret;
   276                 }
   277                 return ((WebPresenter) presenter()).checkArray(ret);
   278             } catch (Error t) {
   279                 t.printStackTrace();
   280                 throw t;
   281             } catch (Exception t) {
   282                 t.printStackTrace();
   283                 throw t;
   284             }
   285         }
   286     }
   287 
   288     private static boolean isJSReady(Object obj) {
   289         if (obj == null) {
   290             return true;
   291         }
   292         if (obj instanceof String) {
   293             return true;
   294         }
   295         if (obj instanceof Number) {
   296             return true;
   297         }
   298         if (obj instanceof JSObject) {
   299             return true;
   300         }
   301         if (obj instanceof Character) {
   302             return true;
   303         }
   304         return false;
   305     }
   306 
   307     private static final class Weak extends WeakReference<Object> {
   308         public Weak(Object referent) {
   309             super(referent);
   310             assert !(referent instanceof Weak);
   311         }
   312     } // end of Weak
   313 }