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