boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java
author Jaroslav Tulach <jtulach@netbeans.org>
Tue, 26 Aug 2014 18:13:30 +0200
changeset 838 bdc3d696dd4a
parent 734 a1b7b481fa42
child 915 15af7ebf1d0e
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.
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@674
    48
import java.net.URL;
jtulach@674
    49
import java.util.ArrayList;
jtulach@674
    50
import java.util.List;
jtulach@680
    51
import java.util.concurrent.Executor;
jtulach@674
    52
import java.util.logging.Level;
jtulach@674
    53
import java.util.logging.Logger;
jtulach@674
    54
import javax.script.Invocable;
jtulach@674
    55
import javax.script.ScriptEngine;
jtulach@674
    56
import javax.script.ScriptEngineManager;
jtulach@674
    57
import javax.script.ScriptException;
jtulach@838
    58
import org.netbeans.html.boot.spi.Fn;
jtulach@838
    59
import org.netbeans.html.boot.spi.Fn.Presenter;
jtulach@674
    60
jtulach@674
    61
/** Implementation of {@link Presenter} that delegates
jtulach@674
    62
 * to Java {@link ScriptEngine scripting} API. The presenter runs headless
jtulach@674
    63
 * without appropriate simulation of browser APIs. Its primary usefulness
jtulach@674
    64
 * is inside testing environments. 
jtulach@674
    65
 * <p>
jtulach@674
    66
 * One can load in browser simulation for example from 
jtulach@674
    67
 * <a href="http://www.envjs.com/">env.js</a>. The best way to achieve so,
jtulach@699
    68
 * is to wait until JDK-8046013 gets fixed....
jtulach@674
    69
 * 
jtulach@674
    70
 *
jtulach@674
    71
 * @author Jaroslav Tulach
jtulach@674
    72
 */
jtulach@699
    73
final class ScriptPresenter 
jtulach@680
    74
implements Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor {
jtulach@689
    75
    private static final Logger LOG = Logger.getLogger(ScriptPresenter.class.getName());
jtulach@674
    76
    private final ScriptEngine eng;
jtulach@693
    77
    private final Executor exc;
jtulach@674
    78
jtulach@693
    79
    public ScriptPresenter(Executor exc) {
jtulach@693
    80
        this.exc = exc;
jtulach@674
    81
        try {
jtulach@674
    82
            eng = new ScriptEngineManager().getEngineByName("javascript");
jtulach@674
    83
            eng.eval("function alert(msg) { Packages.java.lang.System.out.println(msg); };");
jtulach@734
    84
            eng.eval("function confirm(msg) { Packages.java.lang.System.out.println(msg); return true; };");
jtulach@734
    85
            eng.eval("function prompt(msg, txt) { Packages.java.lang.System.out.println(msg + ':' + txt); return txt; };");
jtulach@674
    86
        } catch (ScriptException ex) {
jtulach@674
    87
            throw new IllegalStateException(ex);
jtulach@674
    88
        }
jtulach@674
    89
    }
jtulach@674
    90
jtulach@674
    91
    @Override
jtulach@674
    92
    public Fn defineFn(String code, String... names) {
jtulach@674
    93
        return defineImpl(code, names);
jtulach@674
    94
    }
jtulach@674
    95
    private FnImpl defineImpl(String code, String... names) {
jtulach@674
    96
        StringBuilder sb = new StringBuilder();
jtulach@674
    97
        sb.append("(function() {");
jtulach@674
    98
        sb.append("  return function(");
jtulach@674
    99
        String sep = "";
jtulach@674
   100
        if (names != null) for (String n : names) {
jtulach@674
   101
            sb.append(sep).append(n);
jtulach@674
   102
            sep = ",";
jtulach@674
   103
        }
jtulach@674
   104
        sb.append(") {\n");
jtulach@674
   105
        sb.append(code);
jtulach@674
   106
        sb.append("};");
jtulach@674
   107
        sb.append("})()");
jtulach@674
   108
jtulach@674
   109
        final Object fn;
jtulach@674
   110
        try {
jtulach@674
   111
            fn = eng.eval(sb.toString());
jtulach@674
   112
        } catch (ScriptException ex) {
jtulach@674
   113
            throw new IllegalStateException(ex);
jtulach@674
   114
        }
jtulach@674
   115
        return new FnImpl(this, fn);
jtulach@674
   116
    }
jtulach@674
   117
jtulach@674
   118
    @Override
jtulach@674
   119
    public void displayPage(URL page, Runnable onPageLoad) {
jtulach@682
   120
        try {
jtulach@687
   121
            eng.eval("if (typeof window !== 'undefined') window.location = '" + page + "'");
jtulach@682
   122
        } catch (ScriptException ex) {
jtulach@682
   123
            LOG.log(Level.SEVERE, "Cannot load " + page, ex);
jtulach@682
   124
        }
jtulach@674
   125
        if (onPageLoad != null) {
jtulach@674
   126
            onPageLoad.run();
jtulach@674
   127
        }
jtulach@674
   128
    }
jtulach@674
   129
jtulach@674
   130
    @Override
jtulach@674
   131
    public void loadScript(Reader code) throws Exception {
jtulach@674
   132
        eng.eval(code);
jtulach@674
   133
    }
jtulach@674
   134
    
jtulach@674
   135
    //
jtulach@674
   136
    // array conversions
jtulach@674
   137
    //
jtulach@674
   138
    
jtulach@674
   139
    final Object convertArrays(Object[] arr) throws Exception {
jtulach@674
   140
        for (int i = 0; i < arr.length; i++) {
jtulach@674
   141
            if (arr[i] instanceof Object[]) {
jtulach@674
   142
                arr[i] = convertArrays((Object[]) arr[i]);
jtulach@674
   143
            }
jtulach@674
   144
        }
jtulach@674
   145
        final Object wrapArr = wrapArrFn().invokeImpl(null, false, arr); // NOI18N
jtulach@674
   146
        return wrapArr;
jtulach@674
   147
    }
jtulach@674
   148
jtulach@674
   149
    private FnImpl wrapArrImpl;
jtulach@674
   150
    private FnImpl wrapArrFn() {
jtulach@674
   151
        if (wrapArrImpl == null) {
jtulach@674
   152
            try {
jtulach@674
   153
                wrapArrImpl = defineImpl("return Array.prototype.slice.call(arguments);");
jtulach@674
   154
            } catch (Exception ex) {
jtulach@674
   155
                throw new IllegalStateException(ex);
jtulach@674
   156
            }
jtulach@674
   157
        }
jtulach@674
   158
        return wrapArrImpl;
jtulach@674
   159
    }
jtulach@674
   160
jtulach@674
   161
    final Object checkArray(Object val) throws Exception {
jtulach@674
   162
        final FnImpl fn = arraySizeFn();
jtulach@674
   163
        final Object fnRes = fn.invokeImpl(null, false, val, null);
jtulach@674
   164
        int length = ((Number) fnRes).intValue();
jtulach@674
   165
        if (length == -1) {
jtulach@674
   166
            return val;
jtulach@674
   167
        }
jtulach@674
   168
        Object[] arr = new Object[length];
jtulach@674
   169
        fn.invokeImpl(null, false, val, arr);
jtulach@674
   170
        return arr;
jtulach@674
   171
    }
jtulach@674
   172
    private FnImpl arraySize;
jtulach@674
   173
    private FnImpl arraySizeFn() {
jtulach@674
   174
        if (arraySize == null) {
jtulach@674
   175
            try {
jtulach@674
   176
                arraySize = defineImpl("\n"
jtulach@674
   177
                    + "if (to === null) {\n"
jtulach@674
   178
                    + "  if (Object.prototype.toString.call(arr) === '[object Array]') return arr.length;\n"
jtulach@674
   179
                    + "  else return -1;\n"
jtulach@674
   180
                    + "} else {\n"
jtulach@674
   181
                    + "  var l = arr.length;\n"
jtulach@674
   182
                    + "  for (var i = 0; i < l; i++) to[i] = arr[i];\n"
jtulach@674
   183
                    + "  return l;\n"
jtulach@674
   184
                    + "}", "arr", "to"
jtulach@674
   185
                );
jtulach@674
   186
            } catch (Exception ex) {
jtulach@674
   187
                throw new IllegalStateException(ex);
jtulach@674
   188
            }
jtulach@674
   189
        }
jtulach@674
   190
        return arraySize;
jtulach@674
   191
    }
jtulach@674
   192
jtulach@674
   193
    @Override
jtulach@674
   194
    public Object toJava(Object jsArray) {
jtulach@674
   195
        try {
jtulach@674
   196
            return checkArray(jsArray);
jtulach@674
   197
        } catch (Exception ex) {
jtulach@674
   198
            throw new IllegalStateException(ex);
jtulach@674
   199
        }
jtulach@674
   200
    }
jtulach@674
   201
    
jtulach@674
   202
    @Override
jtulach@674
   203
    public Object toJavaScript(Object toReturn) {
jtulach@674
   204
        if (toReturn instanceof Object[]) {
jtulach@674
   205
            try {
jtulach@674
   206
                return convertArrays((Object[])toReturn);
jtulach@674
   207
            } catch (Exception ex) {
jtulach@674
   208
                throw new IllegalStateException(ex);
jtulach@674
   209
            }
jtulach@674
   210
        } else {
jtulach@674
   211
            return toReturn;
jtulach@674
   212
        }
jtulach@674
   213
    }
jtulach@674
   214
jtulach@680
   215
    @Override
jtulach@693
   216
    public void execute(final Runnable command) {
jtulach@693
   217
        if (Fn.activePresenter() == this) {
jtulach@680
   218
            command.run();
jtulach@693
   219
            return;
jtulach@680
   220
        }
jtulach@693
   221
        
jtulach@693
   222
        class Wrap implements Runnable {
jtulach@693
   223
            public void run() {
jtulach@693
   224
                try (Closeable c = Fn.activate(ScriptPresenter.this)) {
jtulach@693
   225
                    command.run();
jtulach@693
   226
                } catch (IOException ex) {
jtulach@693
   227
                    throw new IllegalStateException(ex);
jtulach@693
   228
                }
jtulach@693
   229
            }
jtulach@693
   230
        }
jtulach@699
   231
        final Runnable wrap = new Wrap();
jtulach@699
   232
        if (exc == null) {
jtulach@699
   233
            wrap.run();
jtulach@699
   234
        } else {
jtulach@699
   235
            exc.execute(wrap);
jtulach@699
   236
        }
jtulach@680
   237
    }
jtulach@680
   238
jtulach@674
   239
    private class FnImpl extends Fn {
jtulach@674
   240
jtulach@674
   241
        private final Object fn;
jtulach@674
   242
jtulach@674
   243
        public FnImpl(Presenter presenter, Object fn) {
jtulach@674
   244
            super(presenter);
jtulach@674
   245
            this.fn = fn;
jtulach@674
   246
        }
jtulach@674
   247
jtulach@674
   248
        @Override
jtulach@674
   249
        public Object invoke(Object thiz, Object... args) throws Exception {
jtulach@674
   250
            return invokeImpl(thiz, true, args);
jtulach@674
   251
        }
jtulach@674
   252
jtulach@674
   253
            final Object invokeImpl(Object thiz, boolean arrayChecks, Object... args) throws Exception {
jtulach@689
   254
                List<Object> all = new ArrayList<>(args.length + 1);
jtulach@674
   255
                all.add(thiz == null ? fn : thiz);
jtulach@674
   256
                for (int i = 0; i < args.length; i++) {
jtulach@674
   257
                    if (arrayChecks) {
jtulach@674
   258
                        if (args[i] instanceof Object[]) {
jtulach@674
   259
                            Object[] arr = (Object[]) args[i];
jtulach@674
   260
                            Object conv = ((ScriptPresenter)presenter()).convertArrays(arr);
jtulach@674
   261
                            args[i] = conv;
jtulach@674
   262
                        }
jtulach@674
   263
                        if (args[i] instanceof Character) {
jtulach@674
   264
                            args[i] = (int)((Character)args[i]);
jtulach@674
   265
                        }
jtulach@674
   266
                    }
jtulach@674
   267
                    all.add(args[i]);
jtulach@674
   268
                }
jtulach@674
   269
                Object ret = ((Invocable)eng).invokeMethod(fn, "call", all.toArray()); // NOI18N
jtulach@674
   270
                if (ret == fn) {
jtulach@674
   271
                    return null;
jtulach@674
   272
                }
jtulach@674
   273
                if (!arrayChecks) {
jtulach@674
   274
                    return ret;
jtulach@674
   275
                }
jtulach@674
   276
                return ((ScriptPresenter)presenter()).checkArray(ret);
jtulach@674
   277
            }
jtulach@674
   278
    }
jtulach@674
   279
    
jtulach@674
   280
}