boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java
author Jaroslav Tulach <jtulach@netbeans.org>
Wed, 13 Jan 2016 05:53:12 +0100
changeset 1040 6429e051b1de
parent 915 15af7ebf1d0e
child 1083 b49546d64269
permissions -rw-r--r--
Better formatting of the function envelope
jtulach@674
     1
/**
jtulach@674
     2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
jtulach@674
     3
 *
jtulach@674
     4
 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
jtulach@674
     5
 *
jtulach@674
     6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
jtulach@674
     7
 * Other names may be trademarks of their respective owners.
jtulach@674
     8
 *
jtulach@674
     9
 * The contents of this file are subject to the terms of either the GNU
jtulach@674
    10
 * General Public License Version 2 only ("GPL") or the Common
jtulach@674
    11
 * Development and Distribution License("CDDL") (collectively, the
jtulach@674
    12
 * "License"). You may not use this file except in compliance with the
jtulach@674
    13
 * License. You can obtain a copy of the License at
jtulach@674
    14
 * http://www.netbeans.org/cddl-gplv2.html
jtulach@674
    15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
jtulach@674
    16
 * specific language governing permissions and limitations under the
jtulach@674
    17
 * License.  When distributing the software, include this License Header
jtulach@674
    18
 * Notice in each file and include the License file at
jtulach@674
    19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
jtulach@674
    20
 * particular file as subject to the "Classpath" exception as provided
jtulach@674
    21
 * by Oracle in the GPL Version 2 section of the License file that
jtulach@674
    22
 * accompanied this code. If applicable, add the following below the
jtulach@674
    23
 * License Header, with the fields enclosed by brackets [] replaced by
jtulach@674
    24
 * your own identifying information:
jtulach@674
    25
 * "Portions Copyrighted [year] [name of copyright owner]"
jtulach@674
    26
 *
jtulach@674
    27
 * Contributor(s):
jtulach@674
    28
 *
jtulach@674
    29
 * The Original Software is NetBeans. The Initial Developer of the Original
jtulach@674
    30
 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
jtulach@674
    31
 *
jtulach@674
    32
 * If you wish your version of this file to be governed by only the CDDL
jtulach@674
    33
 * or only the GPL Version 2, indicate your decision by adding
jtulach@674
    34
 * "[Contributor] elects to include this software in this distribution
jtulach@674
    35
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
jtulach@674
    36
 * single choice of license, a recipient has the option to distribute
jtulach@674
    37
 * your version of this file under either the CDDL, the GPL Version 2 or
jtulach@674
    38
 * to extend the choice of license to its licensees as provided above.
jtulach@674
    39
 * However, if you add GPL Version 2 code and therefore, elected the GPL
jtulach@674
    40
 * Version 2 license, then the option applies only if the new code is
jtulach@674
    41
 * made subject to such option by the copyright holder.
jtulach@674
    42
 */
jtulach@674
    43
package net.java.html.boot.script;
jtulach@674
    44
jtulach@680
    45
import java.io.Closeable;
jtulach@680
    46
import java.io.IOException;
jtulach@674
    47
import java.io.Reader;
jtulach@915
    48
import java.lang.ref.WeakReference;
jtulach@674
    49
import java.net.URL;
jtulach@674
    50
import java.util.ArrayList;
jtulach@674
    51
import java.util.List;
jtulach@680
    52
import java.util.concurrent.Executor;
jtulach@674
    53
import java.util.logging.Level;
jtulach@674
    54
import java.util.logging.Logger;
jtulach@674
    55
import javax.script.Invocable;
jtulach@674
    56
import javax.script.ScriptEngine;
jtulach@674
    57
import javax.script.ScriptEngineManager;
jtulach@674
    58
import javax.script.ScriptException;
jtulach@838
    59
import org.netbeans.html.boot.spi.Fn;
jtulach@838
    60
import org.netbeans.html.boot.spi.Fn.Presenter;
jtulach@674
    61
jtulach@674
    62
/** Implementation of {@link Presenter} that delegates
jtulach@674
    63
 * to Java {@link ScriptEngine scripting} API. The presenter runs headless
jtulach@674
    64
 * without appropriate simulation of browser APIs. Its primary usefulness
jtulach@674
    65
 * is inside testing environments. 
jtulach@674
    66
 * <p>
jtulach@674
    67
 * One can load in browser simulation for example from 
jtulach@674
    68
 * <a href="http://www.envjs.com/">env.js</a>. The best way to achieve so,
jtulach@699
    69
 * is to wait until JDK-8046013 gets fixed....
jtulach@674
    70
 * 
jtulach@674
    71
 *
jtulach@674
    72
 * @author Jaroslav Tulach
jtulach@674
    73
 */
jtulach@915
    74
final class ScriptPresenter implements Fn.KeepAlive,
jtulach@915
    75
Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor {
jtulach@689
    76
    private static final Logger LOG = Logger.getLogger(ScriptPresenter.class.getName());
jtulach@674
    77
    private final ScriptEngine eng;
jtulach@693
    78
    private final Executor exc;
jtulach@674
    79
jtulach@693
    80
    public ScriptPresenter(Executor exc) {
jtulach@693
    81
        this.exc = exc;
jtulach@674
    82
        try {
jtulach@674
    83
            eng = new ScriptEngineManager().getEngineByName("javascript");
jtulach@674
    84
            eng.eval("function alert(msg) { Packages.java.lang.System.out.println(msg); };");
jtulach@734
    85
            eng.eval("function confirm(msg) { Packages.java.lang.System.out.println(msg); return true; };");
jtulach@734
    86
            eng.eval("function prompt(msg, txt) { Packages.java.lang.System.out.println(msg + ':' + txt); return txt; };");
jtulach@674
    87
        } catch (ScriptException ex) {
jtulach@674
    88
            throw new IllegalStateException(ex);
jtulach@674
    89
        }
jtulach@674
    90
    }
jtulach@674
    91
jtulach@674
    92
    @Override
jtulach@674
    93
    public Fn defineFn(String code, String... names) {
jtulach@915
    94
        return defineImpl(code, names, null);
jtulach@674
    95
    }
jtulach@915
    96
jtulach@915
    97
    @Override
jtulach@915
    98
    public Fn defineFn(String code, String[] names, boolean[] keepAlive) {
jtulach@915
    99
        return defineImpl(code, names, keepAlive);
jtulach@915
   100
    }    
jtulach@915
   101
    private FnImpl defineImpl(String code, String[] names, boolean[] keepAlive) {
jtulach@674
   102
        StringBuilder sb = new StringBuilder();
jtulach@1040
   103
        sb.append("(function() {\n");
jtulach@674
   104
        sb.append("  return function(");
jtulach@674
   105
        String sep = "";
jtulach@674
   106
        if (names != null) for (String n : names) {
jtulach@674
   107
            sb.append(sep).append(n);
jtulach@674
   108
            sep = ",";
jtulach@674
   109
        }
jtulach@674
   110
        sb.append(") {\n");
jtulach@674
   111
        sb.append(code);
jtulach@1040
   112
        sb.append("\n  };\n");
jtulach@1040
   113
        sb.append("})()\n");
jtulach@674
   114
jtulach@674
   115
        final Object fn;
jtulach@674
   116
        try {
jtulach@674
   117
            fn = eng.eval(sb.toString());
jtulach@674
   118
        } catch (ScriptException ex) {
jtulach@674
   119
            throw new IllegalStateException(ex);
jtulach@674
   120
        }
jtulach@915
   121
        return new FnImpl(this, fn, keepAlive);
jtulach@674
   122
    }
jtulach@674
   123
jtulach@674
   124
    @Override
jtulach@674
   125
    public void displayPage(URL page, Runnable onPageLoad) {
jtulach@682
   126
        try {
jtulach@687
   127
            eng.eval("if (typeof window !== 'undefined') window.location = '" + page + "'");
jtulach@682
   128
        } catch (ScriptException ex) {
jtulach@682
   129
            LOG.log(Level.SEVERE, "Cannot load " + page, ex);
jtulach@682
   130
        }
jtulach@674
   131
        if (onPageLoad != null) {
jtulach@674
   132
            onPageLoad.run();
jtulach@674
   133
        }
jtulach@674
   134
    }
jtulach@674
   135
jtulach@674
   136
    @Override
jtulach@674
   137
    public void loadScript(Reader code) throws Exception {
jtulach@674
   138
        eng.eval(code);
jtulach@674
   139
    }
jtulach@674
   140
    
jtulach@674
   141
    //
jtulach@674
   142
    // array conversions
jtulach@674
   143
    //
jtulach@674
   144
    
jtulach@674
   145
    final Object convertArrays(Object[] arr) throws Exception {
jtulach@674
   146
        for (int i = 0; i < arr.length; i++) {
jtulach@674
   147
            if (arr[i] instanceof Object[]) {
jtulach@674
   148
                arr[i] = convertArrays((Object[]) arr[i]);
jtulach@674
   149
            }
jtulach@674
   150
        }
jtulach@674
   151
        final Object wrapArr = wrapArrFn().invokeImpl(null, false, arr); // NOI18N
jtulach@674
   152
        return wrapArr;
jtulach@674
   153
    }
jtulach@674
   154
jtulach@674
   155
    private FnImpl wrapArrImpl;
jtulach@674
   156
    private FnImpl wrapArrFn() {
jtulach@674
   157
        if (wrapArrImpl == null) {
jtulach@674
   158
            try {
jtulach@915
   159
                wrapArrImpl = defineImpl("return Array.prototype.slice.call(arguments);", null, null);
jtulach@674
   160
            } catch (Exception ex) {
jtulach@674
   161
                throw new IllegalStateException(ex);
jtulach@674
   162
            }
jtulach@674
   163
        }
jtulach@674
   164
        return wrapArrImpl;
jtulach@674
   165
    }
jtulach@674
   166
jtulach@674
   167
    final Object checkArray(Object val) throws Exception {
jtulach@674
   168
        final FnImpl fn = arraySizeFn();
jtulach@674
   169
        final Object fnRes = fn.invokeImpl(null, false, val, null);
jtulach@674
   170
        int length = ((Number) fnRes).intValue();
jtulach@674
   171
        if (length == -1) {
jtulach@674
   172
            return val;
jtulach@674
   173
        }
jtulach@674
   174
        Object[] arr = new Object[length];
jtulach@674
   175
        fn.invokeImpl(null, false, val, arr);
jtulach@674
   176
        return arr;
jtulach@674
   177
    }
jtulach@674
   178
    private FnImpl arraySize;
jtulach@674
   179
    private FnImpl arraySizeFn() {
jtulach@674
   180
        if (arraySize == null) {
jtulach@674
   181
            try {
jtulach@674
   182
                arraySize = defineImpl("\n"
jtulach@674
   183
                    + "if (to === null) {\n"
jtulach@674
   184
                    + "  if (Object.prototype.toString.call(arr) === '[object Array]') return arr.length;\n"
jtulach@674
   185
                    + "  else return -1;\n"
jtulach@674
   186
                    + "} else {\n"
jtulach@674
   187
                    + "  var l = arr.length;\n"
jtulach@674
   188
                    + "  for (var i = 0; i < l; i++) to[i] = arr[i];\n"
jtulach@674
   189
                    + "  return l;\n"
jtulach@915
   190
                    + "}", new String[] { "arr", "to" }, null
jtulach@674
   191
                );
jtulach@674
   192
            } catch (Exception ex) {
jtulach@674
   193
                throw new IllegalStateException(ex);
jtulach@674
   194
            }
jtulach@674
   195
        }
jtulach@674
   196
        return arraySize;
jtulach@674
   197
    }
jtulach@674
   198
jtulach@674
   199
    @Override
jtulach@674
   200
    public Object toJava(Object jsArray) {
jtulach@915
   201
        if (jsArray instanceof Weak) {
jtulach@915
   202
            jsArray = ((Weak)jsArray).get();
jtulach@915
   203
        }
jtulach@674
   204
        try {
jtulach@674
   205
            return checkArray(jsArray);
jtulach@674
   206
        } catch (Exception ex) {
jtulach@674
   207
            throw new IllegalStateException(ex);
jtulach@674
   208
        }
jtulach@674
   209
    }
jtulach@674
   210
    
jtulach@674
   211
    @Override
jtulach@674
   212
    public Object toJavaScript(Object toReturn) {
jtulach@674
   213
        if (toReturn instanceof Object[]) {
jtulach@674
   214
            try {
jtulach@674
   215
                return convertArrays((Object[])toReturn);
jtulach@674
   216
            } catch (Exception ex) {
jtulach@674
   217
                throw new IllegalStateException(ex);
jtulach@674
   218
            }
jtulach@674
   219
        } else {
jtulach@674
   220
            return toReturn;
jtulach@674
   221
        }
jtulach@674
   222
    }
jtulach@674
   223
jtulach@680
   224
    @Override
jtulach@693
   225
    public void execute(final Runnable command) {
jtulach@693
   226
        if (Fn.activePresenter() == this) {
jtulach@680
   227
            command.run();
jtulach@693
   228
            return;
jtulach@680
   229
        }
jtulach@693
   230
        
jtulach@693
   231
        class Wrap implements Runnable {
jtulach@693
   232
            public void run() {
jtulach@693
   233
                try (Closeable c = Fn.activate(ScriptPresenter.this)) {
jtulach@693
   234
                    command.run();
jtulach@693
   235
                } catch (IOException ex) {
jtulach@693
   236
                    throw new IllegalStateException(ex);
jtulach@693
   237
                }
jtulach@693
   238
            }
jtulach@693
   239
        }
jtulach@699
   240
        final Runnable wrap = new Wrap();
jtulach@699
   241
        if (exc == null) {
jtulach@699
   242
            wrap.run();
jtulach@699
   243
        } else {
jtulach@699
   244
            exc.execute(wrap);
jtulach@699
   245
        }
jtulach@680
   246
    }
jtulach@680
   247
jtulach@674
   248
    private class FnImpl extends Fn {
jtulach@674
   249
jtulach@674
   250
        private final Object fn;
jtulach@915
   251
        private final boolean[] keepAlive;
jtulach@674
   252
jtulach@915
   253
        public FnImpl(Presenter presenter, Object fn, boolean[] keepAlive) {
jtulach@674
   254
            super(presenter);
jtulach@674
   255
            this.fn = fn;
jtulach@915
   256
            this.keepAlive = keepAlive;
jtulach@674
   257
        }
jtulach@674
   258
jtulach@674
   259
        @Override
jtulach@674
   260
        public Object invoke(Object thiz, Object... args) throws Exception {
jtulach@674
   261
            return invokeImpl(thiz, true, args);
jtulach@674
   262
        }
jtulach@674
   263
jtulach@674
   264
            final Object invokeImpl(Object thiz, boolean arrayChecks, Object... args) throws Exception {
jtulach@689
   265
                List<Object> all = new ArrayList<>(args.length + 1);
jtulach@674
   266
                all.add(thiz == null ? fn : thiz);
jtulach@674
   267
                for (int i = 0; i < args.length; i++) {
jtulach@915
   268
                    Object conv = args[i];
jtulach@674
   269
                    if (arrayChecks) {
jtulach@674
   270
                        if (args[i] instanceof Object[]) {
jtulach@674
   271
                            Object[] arr = (Object[]) args[i];
jtulach@915
   272
                            conv = ((ScriptPresenter) presenter()).convertArrays(arr);
jtulach@674
   273
                        }
jtulach@915
   274
                        if (conv != null && keepAlive != null
jtulach@915
   275
                            && !keepAlive[i] && !isJSReady(conv)
jtulach@915
   276
                            && !conv.getClass().getSimpleName().equals("$JsCallbacks$") // NOI18N
jtulach@915
   277
                            ) {
jtulach@915
   278
                            conv = new Weak(conv);
jtulach@915
   279
                        }
jtulach@915
   280
                        if (conv instanceof Character) {
jtulach@915
   281
                            conv = (int)(Character)conv;
jtulach@674
   282
                        }
jtulach@674
   283
                    }
jtulach@915
   284
                    all.add(conv);
jtulach@674
   285
                }
jtulach@674
   286
                Object ret = ((Invocable)eng).invokeMethod(fn, "call", all.toArray()); // NOI18N
jtulach@915
   287
                if (ret instanceof Weak) {
jtulach@915
   288
                    ret = ((Weak)ret).get();
jtulach@915
   289
                }
jtulach@674
   290
                if (ret == fn) {
jtulach@674
   291
                    return null;
jtulach@674
   292
                }
jtulach@674
   293
                if (!arrayChecks) {
jtulach@674
   294
                    return ret;
jtulach@674
   295
                }
jtulach@674
   296
                return ((ScriptPresenter)presenter()).checkArray(ret);
jtulach@674
   297
            }
jtulach@674
   298
    }
jtulach@674
   299
    
jtulach@915
   300
    private static boolean isJSReady(Object obj) {
jtulach@915
   301
        if (obj == null) {
jtulach@915
   302
            return true;
jtulach@915
   303
        }
jtulach@915
   304
        if (obj instanceof String) {
jtulach@915
   305
            return true;
jtulach@915
   306
        }
jtulach@915
   307
        if (obj instanceof Number) {
jtulach@915
   308
            return true;
jtulach@915
   309
        }
jtulach@915
   310
        final String cn = obj.getClass().getName();
jtulach@915
   311
        if (cn.startsWith("jdk.nashorn") || ( // NOI18N
jtulach@915
   312
            cn.contains(".mozilla.") && cn.contains(".Native") // NOI18N
jtulach@915
   313
        )) {
jtulach@915
   314
            return true;
jtulach@915
   315
        }
jtulach@915
   316
        if (obj instanceof Character) {
jtulach@915
   317
            return true;
jtulach@915
   318
        }
jtulach@915
   319
        return false;
jtulach@915
   320
    }    
jtulach@915
   321
    
jtulach@915
   322
    private static final class Weak extends WeakReference<Object> {
jtulach@915
   323
        public Weak(Object referent) {
jtulach@915
   324
            super(referent);
jtulach@915
   325
        }
jtulach@915
   326
    }
jtulach@674
   327
}