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