boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java
author Jaroslav Tulach <jtulach@netbeans.org>
Tue, 03 Jun 2014 20:56:36 +0200
changeset 674 a61fd3f48997
child 680 9fa36fee05e5
permissions -rw-r--r--
Proper presenter that is using javax.script API JavaScript engine
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@674
    45
import java.io.Reader;
jtulach@674
    46
import java.net.URL;
jtulach@674
    47
import java.util.ArrayList;
jtulach@674
    48
import java.util.Arrays;
jtulach@674
    49
import java.util.List;
jtulach@674
    50
import java.util.logging.Level;
jtulach@674
    51
import java.util.logging.Logger;
jtulach@674
    52
import javax.script.Invocable;
jtulach@674
    53
import javax.script.ScriptEngine;
jtulach@674
    54
import javax.script.ScriptEngineManager;
jtulach@674
    55
import javax.script.ScriptException;
jtulach@674
    56
import org.apidesign.html.boot.spi.Fn;
jtulach@674
    57
import org.apidesign.html.boot.spi.Fn.Presenter;
jtulach@674
    58
jtulach@674
    59
/** Implementation of {@link Presenter} that delegates
jtulach@674
    60
 * to Java {@link ScriptEngine scripting} API. The presenter runs headless
jtulach@674
    61
 * without appropriate simulation of browser APIs. Its primary usefulness
jtulach@674
    62
 * is inside testing environments. 
jtulach@674
    63
 * <p>
jtulach@674
    64
 * One can load in browser simulation for example from 
jtulach@674
    65
 * <a href="http://www.envjs.com/">env.js</a>. The best way to achieve so,
jtulach@674
    66
 * is to add dependency on XXX
jtulach@674
    67
 * 
jtulach@674
    68
 *
jtulach@674
    69
 * @author Jaroslav Tulach
jtulach@674
    70
 */
jtulach@674
    71
public final class ScriptPresenter implements Presenter, Fn.FromJavaScript, Fn.ToJavaScript {
jtulach@674
    72
    private final ScriptEngine eng;
jtulach@674
    73
jtulach@674
    74
    public ScriptPresenter() {
jtulach@674
    75
        try {
jtulach@674
    76
            eng = new ScriptEngineManager().getEngineByName("javascript");
jtulach@674
    77
            eng.eval("function alert(msg) { Packages.java.lang.System.out.println(msg); };");
jtulach@674
    78
        } catch (ScriptException ex) {
jtulach@674
    79
            throw new IllegalStateException(ex);
jtulach@674
    80
        }
jtulach@674
    81
    }
jtulach@674
    82
jtulach@674
    83
    @Override
jtulach@674
    84
    public Fn defineFn(String code, String... names) {
jtulach@674
    85
        return defineImpl(code, names);
jtulach@674
    86
    }
jtulach@674
    87
    private FnImpl defineImpl(String code, String... names) {
jtulach@674
    88
        StringBuilder sb = new StringBuilder();
jtulach@674
    89
        sb.append("(function() {");
jtulach@674
    90
        sb.append("  return function(");
jtulach@674
    91
        String sep = "";
jtulach@674
    92
        if (names != null) for (String n : names) {
jtulach@674
    93
            sb.append(sep).append(n);
jtulach@674
    94
            sep = ",";
jtulach@674
    95
        }
jtulach@674
    96
        sb.append(") {\n");
jtulach@674
    97
        sb.append(code);
jtulach@674
    98
        sb.append("};");
jtulach@674
    99
        sb.append("})()");
jtulach@674
   100
jtulach@674
   101
        final Object fn;
jtulach@674
   102
        try {
jtulach@674
   103
            fn = eng.eval(sb.toString());
jtulach@674
   104
        } catch (ScriptException ex) {
jtulach@674
   105
            throw new IllegalStateException(ex);
jtulach@674
   106
        }
jtulach@674
   107
        return new FnImpl(this, fn);
jtulach@674
   108
    }
jtulach@674
   109
jtulach@674
   110
    @Override
jtulach@674
   111
    public void displayPage(URL page, Runnable onPageLoad) {
jtulach@674
   112
        // not really displaying anything
jtulach@674
   113
        if (onPageLoad != null) {
jtulach@674
   114
            onPageLoad.run();
jtulach@674
   115
        }
jtulach@674
   116
    }
jtulach@674
   117
jtulach@674
   118
    @Override
jtulach@674
   119
    public void loadScript(Reader code) throws Exception {
jtulach@674
   120
        eng.eval(code);
jtulach@674
   121
    }
jtulach@674
   122
    
jtulach@674
   123
    //
jtulach@674
   124
    // array conversions
jtulach@674
   125
    //
jtulach@674
   126
    
jtulach@674
   127
    final Object convertArrays(Object[] arr) throws Exception {
jtulach@674
   128
        for (int i = 0; i < arr.length; i++) {
jtulach@674
   129
            if (arr[i] instanceof Object[]) {
jtulach@674
   130
                arr[i] = convertArrays((Object[]) arr[i]);
jtulach@674
   131
            }
jtulach@674
   132
        }
jtulach@674
   133
        final Object wrapArr = wrapArrFn().invokeImpl(null, false, arr); // NOI18N
jtulach@674
   134
        return wrapArr;
jtulach@674
   135
    }
jtulach@674
   136
jtulach@674
   137
    private FnImpl wrapArrImpl;
jtulach@674
   138
    private FnImpl wrapArrFn() {
jtulach@674
   139
        if (wrapArrImpl == null) {
jtulach@674
   140
            try {
jtulach@674
   141
                wrapArrImpl = defineImpl("return Array.prototype.slice.call(arguments);");
jtulach@674
   142
            } catch (Exception ex) {
jtulach@674
   143
                throw new IllegalStateException(ex);
jtulach@674
   144
            }
jtulach@674
   145
        }
jtulach@674
   146
        return wrapArrImpl;
jtulach@674
   147
    }
jtulach@674
   148
jtulach@674
   149
    final Object checkArray(Object val) throws Exception {
jtulach@674
   150
        final FnImpl fn = arraySizeFn();
jtulach@674
   151
        final Object fnRes = fn.invokeImpl(null, false, val, null);
jtulach@674
   152
        int length = ((Number) fnRes).intValue();
jtulach@674
   153
        if (length == -1) {
jtulach@674
   154
            return val;
jtulach@674
   155
        }
jtulach@674
   156
        Object[] arr = new Object[length];
jtulach@674
   157
        fn.invokeImpl(null, false, val, arr);
jtulach@674
   158
        return arr;
jtulach@674
   159
    }
jtulach@674
   160
    private FnImpl arraySize;
jtulach@674
   161
    private FnImpl arraySizeFn() {
jtulach@674
   162
        if (arraySize == null) {
jtulach@674
   163
            try {
jtulach@674
   164
                arraySize = defineImpl("\n"
jtulach@674
   165
                    + "if (to === null) {\n"
jtulach@674
   166
                    + "  if (Object.prototype.toString.call(arr) === '[object Array]') return arr.length;\n"
jtulach@674
   167
                    + "  else return -1;\n"
jtulach@674
   168
                    + "} else {\n"
jtulach@674
   169
                    + "  var l = arr.length;\n"
jtulach@674
   170
                    + "  for (var i = 0; i < l; i++) to[i] = arr[i];\n"
jtulach@674
   171
                    + "  return l;\n"
jtulach@674
   172
                    + "}", "arr", "to"
jtulach@674
   173
                );
jtulach@674
   174
            } catch (Exception ex) {
jtulach@674
   175
                throw new IllegalStateException(ex);
jtulach@674
   176
            }
jtulach@674
   177
        }
jtulach@674
   178
        return arraySize;
jtulach@674
   179
    }
jtulach@674
   180
jtulach@674
   181
    @Override
jtulach@674
   182
    public Object toJava(Object jsArray) {
jtulach@674
   183
        try {
jtulach@674
   184
            return checkArray(jsArray);
jtulach@674
   185
        } catch (Exception ex) {
jtulach@674
   186
            throw new IllegalStateException(ex);
jtulach@674
   187
        }
jtulach@674
   188
    }
jtulach@674
   189
    
jtulach@674
   190
    @Override
jtulach@674
   191
    public Object toJavaScript(Object toReturn) {
jtulach@674
   192
        if (toReturn instanceof Object[]) {
jtulach@674
   193
            try {
jtulach@674
   194
                return convertArrays((Object[])toReturn);
jtulach@674
   195
            } catch (Exception ex) {
jtulach@674
   196
                throw new IllegalStateException(ex);
jtulach@674
   197
            }
jtulach@674
   198
        } else {
jtulach@674
   199
            return toReturn;
jtulach@674
   200
        }
jtulach@674
   201
    }
jtulach@674
   202
jtulach@674
   203
    private class FnImpl extends Fn {
jtulach@674
   204
jtulach@674
   205
        private final Object fn;
jtulach@674
   206
jtulach@674
   207
        public FnImpl(Presenter presenter, Object fn) {
jtulach@674
   208
            super(presenter);
jtulach@674
   209
            this.fn = fn;
jtulach@674
   210
        }
jtulach@674
   211
jtulach@674
   212
        @Override
jtulach@674
   213
        public Object invoke(Object thiz, Object... args) throws Exception {
jtulach@674
   214
            return invokeImpl(thiz, true, args);
jtulach@674
   215
        }
jtulach@674
   216
jtulach@674
   217
            final Object invokeImpl(Object thiz, boolean arrayChecks, Object... args) throws Exception {
jtulach@674
   218
                List<Object> all = new ArrayList<Object>(args.length + 1);
jtulach@674
   219
                all.add(thiz == null ? fn : thiz);
jtulach@674
   220
                for (int i = 0; i < args.length; i++) {
jtulach@674
   221
                    if (arrayChecks) {
jtulach@674
   222
                        if (args[i] instanceof Object[]) {
jtulach@674
   223
                            Object[] arr = (Object[]) args[i];
jtulach@674
   224
                            Object conv = ((ScriptPresenter)presenter()).convertArrays(arr);
jtulach@674
   225
                            args[i] = conv;
jtulach@674
   226
                        }
jtulach@674
   227
                        if (args[i] instanceof Character) {
jtulach@674
   228
                            args[i] = (int)((Character)args[i]);
jtulach@674
   229
                        }
jtulach@674
   230
                    }
jtulach@674
   231
                    all.add(args[i]);
jtulach@674
   232
                }
jtulach@674
   233
                Object ret = ((Invocable)eng).invokeMethod(fn, "call", all.toArray()); // NOI18N
jtulach@674
   234
                if (ret == fn) {
jtulach@674
   235
                    return null;
jtulach@674
   236
                }
jtulach@674
   237
                if (!arrayChecks) {
jtulach@674
   238
                    return ret;
jtulach@674
   239
                }
jtulach@674
   240
                return ((ScriptPresenter)presenter()).checkArray(ret);
jtulach@674
   241
            }
jtulach@674
   242
    }
jtulach@674
   243
    
jtulach@674
   244
}