boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java
author Jaroslav Tulach <jaroslav.tulach@netbeans.org>
Fri, 07 Feb 2014 07:44:34 +0100
changeset 551 7ca2253fa86d
parent 446 6dce58c06f58
child 557 2911034fe047
permissions -rw-r--r--
Updating copyright headers to mention current year
     1 /**
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     5  *
     6  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
     7  * Other names may be trademarks of their respective owners.
     8  *
     9  * The contents of this file are subject to the terms of either the GNU
    10  * General Public License Version 2 only ("GPL") or the Common
    11  * Development and Distribution License("CDDL") (collectively, the
    12  * "License"). You may not use this file except in compliance with the
    13  * License. You can obtain a copy of the License at
    14  * http://www.netbeans.org/cddl-gplv2.html
    15  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    16  * specific language governing permissions and limitations under the
    17  * License.  When distributing the software, include this License Header
    18  * Notice in each file and include the License file at
    19  * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    20  * particular file as subject to the "Classpath" exception as provided
    21  * by Oracle in the GPL Version 2 section of the License file that
    22  * accompanied this code. If applicable, add the following below the
    23  * License Header, with the fields enclosed by brackets [] replaced by
    24  * your own identifying information:
    25  * "Portions Copyrighted [year] [name of copyright owner]"
    26  *
    27  * Contributor(s):
    28  *
    29  * The Original Software is NetBeans. The Initial Developer of the Original
    30  * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    31  *
    32  * If you wish your version of this file to be governed by only the CDDL
    33  * or only the GPL Version 2, indicate your decision by adding
    34  * "[Contributor] elects to include this software in this distribution
    35  * under the [CDDL or GPL Version 2] license." If you do not indicate a
    36  * single choice of license, a recipient has the option to distribute
    37  * your version of this file under either the CDDL, the GPL Version 2 or
    38  * to extend the choice of license to its licensees as provided above.
    39  * However, if you add GPL Version 2 code and therefore, elected the GPL
    40  * Version 2 license, then the option applies only if the new code is
    41  * made subject to such option by the copyright holder.
    42  */
    43 package org.netbeans.html.boot.fx;
    44 
    45 import java.io.BufferedReader;
    46 import java.io.Reader;
    47 import java.net.URL;
    48 import java.util.ArrayList;
    49 import java.util.List;
    50 import java.util.concurrent.Executor;
    51 import java.util.logging.Level;
    52 import java.util.logging.Logger;
    53 import javafx.application.Platform;
    54 import javafx.scene.web.WebEngine;
    55 import javafx.scene.web.WebView;
    56 import netscape.javascript.JSObject;
    57 import org.apidesign.html.boot.spi.Fn;
    58 
    59 /**
    60  *
    61  * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    62  */
    63 public abstract class AbstractFXPresenter 
    64 implements Fn.Presenter, Fn.ToJavaScript, Fn.FromJavaScript, Executor {
    65     static final Logger LOG = Logger.getLogger(FXPresenter.class.getName());
    66     protected static int cnt;
    67     protected List<String> scripts;
    68     protected Runnable onLoad;
    69     protected WebEngine engine;
    70 
    71     @Override
    72     public Fn defineFn(String code, String... names) {
    73         return defineJSFn(code, names);
    74     }
    75     
    76     final JSFn defineJSFn(String code, String... names) {
    77         StringBuilder sb = new StringBuilder();
    78         sb.append("(function() {");
    79         sb.append("  return function(");
    80         String sep = "";
    81         for (String n : names) {
    82             sb.append(sep).append(n);
    83             sep = ",";
    84         }
    85         sb.append(") {\n");
    86         sb.append(code);
    87         sb.append("};");
    88         sb.append("})()");
    89         if (LOG.isLoggable(Level.FINE)) {
    90             LOG.log(Level.FINE, 
    91                 "defining function #{0}:\n{1}\n", 
    92                 new Object[] { ++cnt, code }
    93             );
    94         }
    95         JSObject x = (JSObject) engine.executeScript(sb.toString());
    96         return new JSFn(this, x, cnt);
    97     }
    98 
    99     @Override
   100     public void loadScript(Reader code) throws Exception {
   101         BufferedReader r = new BufferedReader(code);
   102         StringBuilder sb = new StringBuilder();
   103         for (;;) {
   104             String l = r.readLine();
   105             if (l == null) {
   106                 break;
   107             }
   108             sb.append(l).append('\n');
   109         }
   110         final String script = sb.toString();
   111         if (scripts != null) {
   112             scripts.add(script);
   113         }
   114         engine.executeScript(script);
   115     }
   116 
   117     protected final void onPageLoad() {
   118         if (scripts != null) {
   119             for (String s : scripts) {
   120                 engine.executeScript(s);
   121             }
   122         }
   123         onLoad.run();
   124     }
   125 
   126     @Override
   127     public void displayPage(final URL resource, final Runnable onLoad) {
   128         this.onLoad = onLoad;
   129         final WebView view = findView(resource);
   130         this.engine = view.getEngine();
   131         try {
   132             if (FXInspect.initialize(engine)) {
   133                 scripts = new ArrayList<String>();
   134             }
   135         } catch (Throwable ex) {
   136             ex.printStackTrace();
   137         }
   138 
   139         class Run implements Runnable {
   140 
   141             @Override
   142             public void run() {
   143                 if (scripts != null) {
   144                     view.setContextMenuEnabled(true);
   145                 }
   146                 engine.load(resource.toExternalForm());
   147             }
   148         }
   149         Run run = new Run();
   150         if (Platform.isFxApplicationThread()) {
   151             run.run();
   152         } else {
   153             Platform.runLater(run);
   154         }
   155         waitFinished();
   156     }
   157 
   158     protected abstract void waitFinished();
   159 
   160     protected abstract WebView findView(final URL resource);
   161     
   162     final JSObject convertArrays(Object[] arr) {
   163         for (int i = 0; i < arr.length; i++) {
   164             if (arr[i] instanceof Object[]) {
   165                 arr[i] = convertArrays((Object[]) arr[i]);
   166             }
   167         }
   168         final JSObject wrapArr = (JSObject)wrapArrFn().call("array", arr); // NOI18N
   169         return wrapArr;
   170     }
   171 
   172     private JSObject wrapArrImpl;
   173     private final JSObject wrapArrFn() {
   174         if (wrapArrImpl == null) {
   175             try {
   176                 wrapArrImpl = (JSObject)defineJSFn("  var k = {};"
   177                     + "  k.array= function() {"
   178                     + "    return Array.prototype.slice.call(arguments);"
   179                     + "  };"
   180                     + "  return k;"
   181                 ).invokeImpl(null, false);
   182             } catch (Exception ex) {
   183                 throw new IllegalStateException(ex);
   184             }
   185         }
   186         return wrapArrImpl;
   187     }
   188 
   189     final Object checkArray(Object val) {
   190         int length = ((Number) arraySizeFn().call("array", val, null)).intValue();
   191         if (length == -1) {
   192             return val;
   193         }
   194         Object[] arr = new Object[length];
   195         arraySizeFn().call("array", val, arr);
   196         return arr;
   197     }
   198     private JSObject arraySize;
   199     private final JSObject arraySizeFn() {
   200         if (arraySize == null) {
   201             try {
   202                 arraySize = (JSObject)defineJSFn("  var k = {};"
   203                     + "  k.array = function(arr, to) {"
   204                     + "    if (to === null) {"
   205                     + "      if (Object.prototype.toString.call(arr) === '[object Array]') return arr.length;"
   206                     + "      else return -1;"
   207                     + "    } else {"
   208                     + "      var l = arr.length;"
   209                     + "      for (var i = 0; i < l; i++) to[i] = arr[i];"
   210                     + "      return l;"
   211                     + "    }"
   212                     + "  };"
   213                     + "  return k;"
   214                 ).invokeImpl(null, false);
   215             } catch (Exception ex) {
   216                 throw new IllegalStateException(ex);
   217             }
   218         }
   219         return arraySize;
   220     }
   221 
   222     @Override
   223     public Object toJava(Object jsArray) {
   224         return checkArray(jsArray);
   225     }
   226     
   227     @Override
   228     public Object toJavaScript(Object toReturn) {
   229         if (toReturn instanceof Object[]) {
   230             return convertArrays((Object[])toReturn);
   231         } else {
   232             return toReturn;
   233         }
   234     }
   235     
   236     @Override public void execute(Runnable r) {
   237         if (Platform.isFxApplicationThread()) {
   238             r.run();
   239         } else {
   240             Platform.runLater(r);
   241         }
   242     }
   243 
   244     private static final class JSFn extends Fn {
   245 
   246         private final JSObject fn;
   247         private static int call;
   248         private final int id;
   249 
   250         public JSFn(AbstractFXPresenter p, JSObject fn, int id) {
   251             super(p);
   252             this.fn = fn;
   253             this.id = id;
   254         }
   255 
   256         @Override
   257         public Object invoke(Object thiz, Object... args) throws Exception {
   258             return invokeImpl(thiz, true, args);
   259         }
   260         
   261         final Object invokeImpl(Object thiz, boolean arrayChecks, Object... args) throws Exception {
   262             try {
   263                 if (LOG.isLoggable(Level.FINE)) {
   264                     LOG.log(Level.FINE, "calling {0} function #{1}", new Object[]{++call, id});
   265                 }
   266                 List<Object> all = new ArrayList<Object>(args.length + 1);
   267                 all.add(thiz == null ? fn : thiz);
   268                 for (int i = 0; i < args.length; i++) {
   269                     if (arrayChecks && args[i] instanceof Object[]) {
   270                         Object[] arr = (Object[]) args[i];
   271                         Object conv = ((AbstractFXPresenter)presenter()).convertArrays(arr);
   272                         args[i] = conv;
   273                     }
   274                     all.add(args[i]);
   275                 }
   276                 Object ret = fn.call("call", all.toArray()); // NOI18N
   277                 if (ret == fn) {
   278                     return null;
   279                 }
   280                 if (!arrayChecks) {
   281                     return ret;
   282                 }
   283                 return ((AbstractFXPresenter)presenter()).checkArray(ret);
   284             } catch (Error t) {
   285                 t.printStackTrace();
   286                 throw t;
   287             } catch (Exception t) {
   288                 t.printStackTrace();
   289                 throw t;
   290             }
   291         }
   292     }
   293     
   294 }