boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java
author Jaroslav Tulach <jtulach@netbeans.org>
Tue, 26 Aug 2014 18:13:30 +0200
changeset 838 bdc3d696dd4a
parent 738 5b88d9ecc21c
child 907 dbd7ab3a4714
permissions -rw-r--r--
During the API review process (bug 246133) the reviewers decided that in order to include html4j to NetBeans Platform, we need to stop using org.apidesign namespace and switch to NetBeans one. Repackaging all SPI packages into org.netbeans.html.smthng.spi.
     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.Closeable;
    47 import java.io.IOException;
    48 import java.io.Reader;
    49 import java.net.URL;
    50 import java.util.ArrayList;
    51 import java.util.Arrays;
    52 import java.util.List;
    53 import java.util.concurrent.Executor;
    54 import java.util.logging.Level;
    55 import java.util.logging.Logger;
    56 import javafx.application.Platform;
    57 import javafx.collections.ObservableList;
    58 import javafx.scene.Node;
    59 import javafx.scene.Parent;
    60 import javafx.scene.layout.BorderPane;
    61 import javafx.scene.web.WebEngine;
    62 import javafx.scene.web.WebView;
    63 import netscape.javascript.JSObject;
    64 import org.netbeans.html.boot.spi.Fn;
    65 
    66 /**
    67  *
    68  * @author Jaroslav Tulach
    69  */
    70 public abstract class AbstractFXPresenter 
    71 implements Fn.Presenter, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable {
    72     static final Logger LOG = Logger.getLogger(FXPresenter.class.getName());
    73     protected static int cnt;
    74     protected Runnable onLoad;
    75     protected WebEngine engine;
    76 
    77     // transient - e.g. not cloneable
    78     private JSObject arraySize;
    79     private JSObject wrapArrImpl;
    80 
    81     @Override
    82     protected AbstractFXPresenter clone() {
    83         try {
    84             AbstractFXPresenter p = (AbstractFXPresenter) super.clone();
    85             p.arraySize = null;
    86             p.wrapArrImpl = null;
    87             return p;
    88         } catch (CloneNotSupportedException ex) {
    89             throw new IllegalStateException(ex);
    90         }
    91     }
    92     
    93     @Override
    94     public Fn defineFn(String code, String... names) {
    95         return defineJSFn(code, names);
    96     }
    97     
    98     final JSFn defineJSFn(String code, String... names) {
    99         StringBuilder sb = new StringBuilder();
   100         sb.append("(function() {");
   101         sb.append("  return function(");
   102         String sep = "";
   103         for (String n : names) {
   104             sb.append(sep).append(n);
   105             sep = ",";
   106         }
   107         sb.append(") {\n");
   108         sb.append(code);
   109         sb.append("};");
   110         sb.append("})()");
   111         if (LOG.isLoggable(Level.FINE)) {
   112             LOG.log(Level.FINE, 
   113                 "defining function #{0}:\n{1}\n", 
   114                 new Object[] { ++cnt, code }
   115             );
   116         }
   117         JSObject x = (JSObject) engine.executeScript(sb.toString());
   118         return new JSFn(this, x, cnt);
   119     }
   120 
   121     @Override
   122     public void loadScript(Reader code) throws Exception {
   123         BufferedReader r = new BufferedReader(code);
   124         StringBuilder sb = new StringBuilder();
   125         for (;;) {
   126             String l = r.readLine();
   127             if (l == null) {
   128                 break;
   129             }
   130             sb.append(l).append('\n');
   131         }
   132         final String script = sb.toString();
   133         engine.executeScript(script);
   134     }
   135 
   136     protected final void onPageLoad() {
   137         Closeable c = Fn.activate(this.clone());
   138         try {
   139             onLoad.run();
   140         } finally {
   141             try {
   142                 c.close();
   143             } catch (IOException ex) {
   144                 LOG.log(Level.SEVERE, null, ex);
   145             }
   146         }
   147     }
   148 
   149     @Override
   150     public void displayPage(final URL resource, final Runnable onLoad) {
   151         this.onLoad = onLoad;
   152         final WebView view = findView(resource);
   153         this.engine = view.getEngine();
   154         boolean inspectOn = false;
   155         try {
   156             if (FXInspect.initialize(engine)) {
   157                 inspectOn = true;
   158             }
   159         } catch (Throwable ex) {
   160             ex.printStackTrace();
   161         }
   162         final boolean isInspectOn = inspectOn;
   163 
   164         class Run implements Runnable {
   165 
   166             @Override
   167             public void run() {
   168                 if (isInspectOn) {
   169                     view.setContextMenuEnabled(true);
   170                     final Parent p = view.getParent();
   171                     if (p instanceof BorderPane) {
   172                         BorderPane bp = (BorderPane) p;
   173                         if (bp.getTop() == null) {
   174                             bp.setTop(new FXToolbar(view, bp));
   175                         }
   176                     }
   177                 }
   178                 engine.load(resource.toExternalForm());
   179             }
   180         }
   181         Run run = new Run();
   182         if (Platform.isFxApplicationThread()) {
   183             run.run();
   184         } else {
   185             Platform.runLater(run);
   186         }
   187         waitFinished();
   188     }
   189 
   190     protected abstract void waitFinished();
   191 
   192     protected abstract WebView findView(final URL resource);
   193     
   194     final JSObject convertArrays(Object[] arr) {
   195         for (int i = 0; i < arr.length; i++) {
   196             if (arr[i] instanceof Object[]) {
   197                 arr[i] = convertArrays((Object[]) arr[i]);
   198             }
   199         }
   200         final JSObject wrapArr = (JSObject)wrapArrFn().call("array", arr); // NOI18N
   201         return wrapArr;
   202     }
   203 
   204     private final JSObject wrapArrFn() {
   205         if (wrapArrImpl == null) {
   206             try {
   207                 wrapArrImpl = (JSObject)defineJSFn("  var k = {};"
   208                     + "  k.array= function() {"
   209                     + "    return Array.prototype.slice.call(arguments);"
   210                     + "  };"
   211                     + "  return k;"
   212                 ).invokeImpl(null, false);
   213             } catch (Exception ex) {
   214                 throw new IllegalStateException(ex);
   215             }
   216         }
   217         return wrapArrImpl;
   218     }
   219 
   220     final Object checkArray(Object val) {
   221         int length = ((Number) arraySizeFn().call("array", val, null)).intValue();
   222         if (length == -1) {
   223             return val;
   224         }
   225         Object[] arr = new Object[length];
   226         arraySizeFn().call("array", val, arr);
   227         return arr;
   228     }
   229     private final JSObject arraySizeFn() {
   230         if (arraySize == null) {
   231             try {
   232                 arraySize = (JSObject)defineJSFn("  var k = {};"
   233                     + "  k.array = function(arr, to) {"
   234                     + "    if (to === null) {"
   235                     + "      if (Object.prototype.toString.call(arr) === '[object Array]') return arr.length;"
   236                     + "      else return -1;"
   237                     + "    } else {"
   238                     + "      var l = arr.length;"
   239                     + "      for (var i = 0; i < l; i++) to[i] = arr[i];"
   240                     + "      return l;"
   241                     + "    }"
   242                     + "  };"
   243                     + "  return k;"
   244                 ).invokeImpl(null, false);
   245             } catch (Exception ex) {
   246                 throw new IllegalStateException(ex);
   247             }
   248         }
   249         return arraySize;
   250     }
   251 
   252     @Override
   253     public Object toJava(Object jsArray) {
   254         return checkArray(jsArray);
   255     }
   256     
   257     @Override
   258     public Object toJavaScript(Object toReturn) {
   259         if (toReturn instanceof Object[]) {
   260             return convertArrays((Object[])toReturn);
   261         } else {
   262             return toReturn;
   263         }
   264     }
   265     
   266     @Override public void execute(final Runnable r) {
   267         if (Platform.isFxApplicationThread()) {
   268             Closeable c = Fn.activate(this);
   269             try {
   270                 r.run();
   271             } finally {
   272                 try {
   273                     c.close();
   274                 } catch (IOException ex) {
   275                     // ignore
   276                 }
   277             }                
   278         } else {
   279             class Wrap implements Runnable {
   280                 @Override
   281                 public void run() {
   282                     Closeable c = Fn.activate(AbstractFXPresenter.this);
   283                     try {
   284                         r.run();
   285                     } finally {
   286                         try {
   287                             c.close();
   288                         } catch (IOException ex) {
   289                             // ignore
   290                         }
   291                     }                
   292                 }
   293             }
   294             Platform.runLater(new Wrap());
   295         }
   296     }
   297 
   298     private static final class JSFn extends Fn {
   299 
   300         private final JSObject fn;
   301         private static int call;
   302         private final int id;
   303 
   304         public JSFn(AbstractFXPresenter p, JSObject fn, int id) {
   305             super(p);
   306             this.fn = fn;
   307             this.id = id;
   308         }
   309 
   310         @Override
   311         public Object invoke(Object thiz, Object... args) throws Exception {
   312             return invokeImpl(thiz, true, args);
   313         }
   314         
   315         final Object invokeImpl(Object thiz, boolean arrayChecks, Object... args) throws Exception {
   316             try {
   317                 if (LOG.isLoggable(Level.FINE)) {
   318                     LOG.log(Level.FINE, "calling {0} function #{1}", new Object[]{++call, id});
   319                     LOG.log(Level.FINER, "  thiz  : {0}", thiz);
   320                     LOG.log(Level.FINER, "  params: {0}", Arrays.asList(args));
   321                 }
   322                 List<Object> all = new ArrayList<Object>(args.length + 1);
   323                 all.add(thiz == null ? fn : thiz);
   324                 for (int i = 0; i < args.length; i++) {
   325                     if (arrayChecks && args[i] instanceof Object[]) {
   326                         Object[] arr = (Object[]) args[i];
   327                         Object conv = ((AbstractFXPresenter)presenter()).convertArrays(arr);
   328                         args[i] = conv;
   329                     }
   330                     all.add(args[i]);
   331                 }
   332                 Object ret = fn.call("call", all.toArray()); // NOI18N
   333                 if (ret == fn) {
   334                     return null;
   335                 }
   336                 if (!arrayChecks) {
   337                     return ret;
   338                 }
   339                 return ((AbstractFXPresenter)presenter()).checkArray(ret);
   340             } catch (Error t) {
   341                 t.printStackTrace();
   342                 throw t;
   343             } catch (Exception t) {
   344                 t.printStackTrace();
   345                 throw t;
   346             }
   347         }
   348     }
   349     
   350 }