src/jdk/nashorn/api/scripting/NashornScriptEngine.java

Fri, 19 Apr 2013 16:11:16 +0200

author
lagergren
date
Fri, 19 Apr 2013 16:11:16 +0200
changeset 211
3a209cbd1d8f
parent 142
3b0a0d9d51f0
child 252
544e17632e96
permissions
-rw-r--r--

8010701: Immutable nodes - final iteration
Reviewed-by: sundar, hannesw, jlaskey

     1 /*
     2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Oracle designates this
     8  * particular file as subject to the "Classpath" exception as provided
     9  * by Oracle in the LICENSE file that accompanied this code.
    10  *
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    14  * version 2 for more details (a copy is included in the LICENSE file that
    15  * accompanied this code).
    16  *
    17  * You should have received a copy of the GNU General Public License version
    18  * 2 along with this work; if not, write to the Free Software Foundation,
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    20  *
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    22  * or visit www.oracle.com if you need additional information or have any
    23  * questions.
    24  */
    26 package jdk.nashorn.api.scripting;
    28 import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError;
    29 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
    31 import java.io.IOException;
    32 import java.io.InputStream;
    33 import java.io.InputStreamReader;
    34 import java.io.Reader;
    35 import java.lang.reflect.Method;
    36 import java.net.URL;
    37 import java.security.AccessController;
    38 import java.security.PrivilegedAction;
    39 import java.security.PrivilegedActionException;
    40 import java.security.PrivilegedExceptionAction;
    41 import javax.script.AbstractScriptEngine;
    42 import javax.script.Bindings;
    43 import javax.script.Compilable;
    44 import javax.script.CompiledScript;
    45 import javax.script.Invocable;
    46 import javax.script.ScriptContext;
    47 import javax.script.ScriptEngine;
    48 import javax.script.ScriptEngineFactory;
    49 import javax.script.ScriptException;
    50 import jdk.nashorn.internal.runtime.Context;
    51 import jdk.nashorn.internal.runtime.ErrorManager;
    52 import jdk.nashorn.internal.runtime.GlobalObject;
    53 import jdk.nashorn.internal.runtime.Property;
    54 import jdk.nashorn.internal.runtime.ScriptFunction;
    55 import jdk.nashorn.internal.runtime.ScriptObject;
    56 import jdk.nashorn.internal.runtime.ScriptRuntime;
    57 import jdk.nashorn.internal.runtime.Source;
    58 import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory;
    59 import jdk.nashorn.internal.runtime.options.Options;
    61 /**
    62  * JSR-223 compliant script engine for Nashorn. Instances are not created directly, but rather returned through
    63  * {@link NashornScriptEngineFactory#getScriptEngine()}. Note that this engine implements the {@link Compilable} and
    64  * {@link Invocable} interfaces, allowing for efficient precompilation and repeated execution of scripts.
    65  * @see NashornScriptEngineFactory
    66  */
    68 public final class NashornScriptEngine extends AbstractScriptEngine implements Compilable, Invocable {
    70     private final ScriptEngineFactory factory;
    71     private final Context             nashornContext;
    72     private final ScriptObject        global;
    74     // default options passed to Nashorn Options object
    75     private static final String[] DEFAULT_OPTIONS = new String[] { "-scripting", "-af", "-doe" };
    77     NashornScriptEngine(final NashornScriptEngineFactory factory, final ClassLoader appLoader) {
    78         this(factory, DEFAULT_OPTIONS, appLoader);
    79     }
    81     @SuppressWarnings("LeakingThisInConstructor")
    82     NashornScriptEngine(final NashornScriptEngineFactory factory, final String[] args, final ClassLoader appLoader) {
    83         this.factory = factory;
    84         final Options options = new Options("nashorn");
    85         options.process(args);
    87         // throw ParseException on first error from script
    88         final ErrorManager errMgr = new Context.ThrowErrorManager();
    89         // create new Nashorn Context
    90         this.nashornContext = AccessController.doPrivileged(new PrivilegedAction<Context>() {
    91             @Override
    92             public Context run() {
    93                 try {
    94                     return new Context(options, errMgr, appLoader);
    95                 } catch (final RuntimeException e) {
    96                     if (Context.DEBUG) {
    97                         e.printStackTrace();
    98                     }
    99                     throw e;
   100                 }
   101             }
   102         });
   104         // create new global object
   105         this.global =  createNashornGlobal();
   106         // set the default engine scope for the default context
   107         context.setBindings(new ScriptObjectMirror(global, global), ScriptContext.ENGINE_SCOPE);
   109         // evaluate engine initial script
   110         try {
   111             evalEngineScript();
   112         } catch (final ScriptException e) {
   113             if (Context.DEBUG) {
   114                 e.printStackTrace();
   115             }
   116             throw new RuntimeException(e);
   117         }
   118     }
   120     @Override
   121     public Object eval(final Reader reader, final ScriptContext ctxt) throws ScriptException {
   122         try {
   123             if (reader instanceof URLReader) {
   124                 final URL url = ((URLReader)reader).getURL();
   125                 return evalImpl(compileImpl(new Source(url.toString(), url), ctxt), ctxt);
   126             }
   127             return evalImpl(Source.readFully(reader), ctxt);
   128         } catch (final IOException e) {
   129             throw new ScriptException(e);
   130         }
   131     }
   133     @Override
   134     public Object eval(final String script, final ScriptContext ctxt) throws ScriptException {
   135         return evalImpl(script.toCharArray(), ctxt);
   136     }
   138     @Override
   139     public ScriptEngineFactory getFactory() {
   140         return factory;
   141     }
   143     @Override
   144     public Bindings createBindings() {
   145         final ScriptObject newGlobal = createNashornGlobal();
   146         return new ScriptObjectMirror(newGlobal, newGlobal);
   147     }
   149     // Compilable methods
   151     @Override
   152     public CompiledScript compile(final Reader reader) throws ScriptException {
   153         try {
   154             return asCompiledScript(compileImpl(Source.readFully(reader), context));
   155         } catch (final IOException e) {
   156             throw new ScriptException(e);
   157         }
   158     }
   160     @Override
   161     public CompiledScript compile(final String str) throws ScriptException {
   162         return asCompiledScript(compileImpl(str.toCharArray(), context));
   163     }
   165     // Invocable methods
   167     @Override
   168     public Object invokeFunction(final String name, final Object... args)
   169             throws ScriptException, NoSuchMethodException {
   170         return invokeImpl(null, name, args);
   171     }
   173     @Override
   174     public Object invokeMethod(final Object self, final String name, final Object... args)
   175             throws ScriptException, NoSuchMethodException {
   176         if (self == null) {
   177             throw new IllegalArgumentException("script object can not be null");
   178         }
   179         return invokeImpl(self, name, args);
   180     }
   182     private <T> T getInterfaceInner(final Object self, final Class<T> clazz) {
   183         final ScriptObject realSelf;
   184         final ScriptObject ctxtGlobal = getNashornGlobalFrom(context);
   185         if(self == null) {
   186             realSelf = ctxtGlobal;
   187         } else if (!(self instanceof ScriptObject)) {
   188             realSelf = (ScriptObject)ScriptObjectMirror.unwrap(self, ctxtGlobal);
   189         } else {
   190             realSelf = (ScriptObject)self;
   191         }
   192         try {
   193             final ScriptObject oldGlobal = getNashornGlobal();
   194             try {
   195                 if(oldGlobal != ctxtGlobal) {
   196                     setNashornGlobal(ctxtGlobal);
   197                 }
   199                 if (! isInterfaceImplemented(clazz, realSelf)) {
   200                     return null;
   201                 }
   202                 return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz).invoke(realSelf));
   203             } finally {
   204                 if(oldGlobal != ctxtGlobal) {
   205                     setNashornGlobal(oldGlobal);
   206                 }
   207             }
   208         } catch(final RuntimeException|Error e) {
   209             throw e;
   210         } catch(final Throwable t) {
   211             throw new RuntimeException(t);
   212         }
   213     }
   215     @Override
   216     public <T> T getInterface(final Class<T> clazz) {
   217         return getInterfaceInner(null, clazz);
   218     }
   220     @Override
   221     public <T> T getInterface(final Object self, final Class<T> clazz) {
   222         if (self == null) {
   223             throw new IllegalArgumentException("script object can not be null");
   224         }
   225         return getInterfaceInner(self, clazz);
   226     }
   228     // These are called from the "engine.js" script
   230     /**
   231      * This hook is used to search js global variables exposed from Java code.
   232      *
   233      * @param self 'this' passed from the script
   234      * @param ctxt current ScriptContext in which name is searched
   235      * @param name name of the variable searched
   236      * @return the value of the named variable
   237      */
   238     public Object __noSuchProperty__(final Object self, final ScriptContext ctxt, final String name) {
   239         final int scope = ctxt.getAttributesScope(name);
   240         final ScriptObject ctxtGlobal = getNashornGlobalFrom(ctxt);
   241         if (scope != -1) {
   242             return ScriptObjectMirror.unwrap(ctxt.getAttribute(name, scope), ctxtGlobal);
   243         }
   245         if (self == UNDEFINED) {
   246             // scope access and so throw ReferenceError
   247             throw referenceError(ctxtGlobal, "not.defined", name);
   248         }
   250         return UNDEFINED;
   251     }
   253     private ScriptObject getNashornGlobalFrom(final ScriptContext ctxt) {
   254         final Bindings bindings = ctxt.getBindings(ScriptContext.ENGINE_SCOPE);
   255         if (bindings instanceof ScriptObjectMirror) {
   256              ScriptObject sobj = ((ScriptObjectMirror)bindings).getScriptObject();
   257              if (sobj instanceof GlobalObject) {
   258                  return sobj;
   259              }
   260         }
   262         // didn't find global object from context given - return the engine-wide global
   263         return global;
   264     }
   266     private ScriptObject createNashornGlobal() {
   267         final ScriptObject newGlobal = AccessController.doPrivileged(new PrivilegedAction<ScriptObject>() {
   268             @Override
   269             public ScriptObject run() {
   270                 try {
   271                     return nashornContext.newGlobal();
   272                 } catch (final RuntimeException e) {
   273                     if (Context.DEBUG) {
   274                         e.printStackTrace();
   275                     }
   276                     throw e;
   277                 }
   278             }
   279         });
   281         nashornContext.initGlobal(newGlobal);
   283         // current ScriptContext exposed as "context"
   284         newGlobal.addOwnProperty("context", Property.NOT_ENUMERABLE, UNDEFINED);
   285         // current ScriptEngine instance exposed as "engine". We added @SuppressWarnings("LeakingThisInConstructor") as
   286         // NetBeans identifies this assignment as such a leak - this is a false positive as we're setting this property
   287         // in the Global of a Context we just created - both the Context and the Global were just created and can not be
   288         // seen from another thread outside of this constructor.
   289         newGlobal.addOwnProperty("engine", Property.NOT_ENUMERABLE, this);
   290         // global script arguments with undefined value
   291         newGlobal.addOwnProperty("arguments", Property.NOT_ENUMERABLE, UNDEFINED);
   292         // file name default is null
   293         newGlobal.addOwnProperty(ScriptEngine.FILENAME, Property.NOT_ENUMERABLE, null);
   294         return newGlobal;
   295     }
   297     private void evalEngineScript() throws ScriptException {
   298         evalSupportScript("resources/engine.js", NashornException.ENGINE_SCRIPT_SOURCE_NAME);
   299     }
   301     private void evalSupportScript(final String script, final String name) throws ScriptException {
   302         try {
   303             final InputStream is = AccessController.doPrivileged(
   304                     new PrivilegedExceptionAction<InputStream>() {
   305                         @Override
   306                         public InputStream run() throws Exception {
   307                             final URL url = NashornScriptEngine.class.getResource(script);
   308                             return url.openStream();
   309                         }
   310                     });
   311             put(ScriptEngine.FILENAME, name);
   312             try (final InputStreamReader isr = new InputStreamReader(is)) {
   313                 eval(isr);
   314             }
   315         } catch (final PrivilegedActionException | IOException e) {
   316             throw new ScriptException(e);
   317         } finally {
   318             put(ScriptEngine.FILENAME, null);
   319         }
   320     }
   322     // scripts should see "context" and "engine" as variables
   323     private void setContextVariables(final ScriptContext ctxt) {
   324         ctxt.setAttribute("context", ctxt, ScriptContext.ENGINE_SCOPE);
   325         final ScriptObject ctxtGlobal = getNashornGlobalFrom(ctxt);
   326         ctxtGlobal.set("context", ctxt, false);
   327         Object args = ScriptObjectMirror.unwrap(ctxt.getAttribute("arguments"), ctxtGlobal);
   328         if (args == null || args == UNDEFINED) {
   329             args = ScriptRuntime.EMPTY_ARRAY;
   330         }
   331         // if no arguments passed, expose it
   332         args = ((GlobalObject)ctxtGlobal).wrapAsObject(args);
   333         ctxtGlobal.set("arguments", args, false);
   334     }
   336     private Object invokeImpl(final Object selfObject, final String name, final Object... args) throws ScriptException, NoSuchMethodException {
   337         final ScriptObject oldGlobal     = getNashornGlobal();
   338         final ScriptObject ctxtGlobal    = getNashornGlobalFrom(context);
   339         final boolean globalChanged = (oldGlobal != ctxtGlobal);
   341         Object self = globalChanged? ScriptObjectMirror.wrap(selfObject, oldGlobal) : selfObject;
   343         try {
   344             if (globalChanged) {
   345                 setNashornGlobal(ctxtGlobal);
   346             }
   348             ScriptObject sobj;
   349             Object       value = null;
   351             self = ScriptObjectMirror.unwrap(self, ctxtGlobal);
   353             // FIXME: should convert when self is not ScriptObject
   354             if (self instanceof ScriptObject) {
   355                 sobj = (ScriptObject)self;
   356                 value = sobj.get(name);
   357             } else if (self == null) {
   358                 self  = ctxtGlobal;
   359                 sobj  = ctxtGlobal;
   360                 value = sobj.get(name);
   361             }
   363             if (value instanceof ScriptFunction) {
   364                 final Object res;
   365                 try {
   366                     final Object[] modArgs = globalChanged? ScriptObjectMirror.wrapArray(args, oldGlobal) : args;
   367                     res = ScriptRuntime.checkAndApply((ScriptFunction)value, self, ScriptObjectMirror.unwrapArray(modArgs, ctxtGlobal));
   368                 } catch (final Exception e) {
   369                     throwAsScriptException(e);
   370                     throw new AssertionError("should not reach here");
   371                 }
   372                 return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(res, ctxtGlobal));
   373             }
   375             throw new NoSuchMethodException(name);
   376         } finally {
   377             if (globalChanged) {
   378                 setNashornGlobal(oldGlobal);
   379             }
   380         }
   381     }
   383     private Object evalImpl(final char[] buf, final ScriptContext ctxt) throws ScriptException {
   384         return evalImpl(compileImpl(buf, ctxt), ctxt);
   385     }
   387     private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt) throws ScriptException {
   388         if (script == null) {
   389             return null;
   390         }
   391         final ScriptObject oldGlobal = getNashornGlobal();
   392         final ScriptObject ctxtGlobal = getNashornGlobalFrom(ctxt);
   393         final boolean globalChanged = (oldGlobal != ctxtGlobal);
   394         try {
   395             if (globalChanged) {
   396                 setNashornGlobal(ctxtGlobal);
   397             }
   399             setContextVariables(ctxt);
   400             return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal));
   401         } catch (final Exception e) {
   402             throwAsScriptException(e);
   403             throw new AssertionError("should not reach here");
   404         } finally {
   405             if (globalChanged) {
   406                 setNashornGlobal(oldGlobal);
   407             }
   408         }
   409     }
   411     private static void throwAsScriptException(final Exception e) throws ScriptException {
   412         if (e instanceof ScriptException) {
   413             throw (ScriptException)e;
   414         } else if (e instanceof NashornException) {
   415             final NashornException ne = (NashornException)e;
   416             final ScriptException se = new ScriptException(
   417                 ne.getMessage(), ne.getFileName(),
   418                 ne.getLineNumber(), ne.getColumnNumber());
   419             se.initCause(e);
   420             throw se;
   421         } else if (e instanceof RuntimeException) {
   422             throw (RuntimeException)e;
   423         } else {
   424             // wrap any other exception as ScriptException
   425             throw new ScriptException(e);
   426         }
   427     }
   429     private CompiledScript asCompiledScript(final ScriptFunction script) {
   430         return new CompiledScript() {
   431             @Override
   432             public Object eval(final ScriptContext ctxt) throws ScriptException {
   433                 return evalImpl(script, ctxt);
   434             }
   435             @Override
   436             public ScriptEngine getEngine() {
   437                 return NashornScriptEngine.this;
   438             }
   439         };
   440     }
   442     private ScriptFunction compileImpl(final char[] buf, final ScriptContext ctxt) throws ScriptException {
   443         final Object val = ctxt.getAttribute(ScriptEngine.FILENAME);
   444         final String fileName = (val != null) ? val.toString() : "<eval>";
   445         return compileImpl(new Source(fileName, buf), ctxt);
   446     }
   448     private ScriptFunction compileImpl(final Source source, final ScriptContext ctxt) throws ScriptException {
   449         final ScriptObject oldGlobal = getNashornGlobal();
   450         final ScriptObject ctxtGlobal = getNashornGlobalFrom(ctxt);
   451         final boolean globalChanged = (oldGlobal != ctxtGlobal);
   452         try {
   453             if (globalChanged) {
   454                 setNashornGlobal(ctxtGlobal);
   455             }
   457             return nashornContext.compileScript(source, ctxtGlobal);
   458         } catch (final Exception e) {
   459             throwAsScriptException(e);
   460             throw new AssertionError("should not reach here");
   461         } finally {
   462             if (globalChanged) {
   463                 setNashornGlobal(oldGlobal);
   464             }
   465         }
   466     }
   468     private static boolean isInterfaceImplemented(final Class<?> iface, final ScriptObject sobj) {
   469         for (final Method method : iface.getMethods()) {
   470             // ignore methods of java.lang.Object class
   471             if (method.getDeclaringClass() == Object.class) {
   472                 continue;
   473             }
   475             Object obj = sobj.get(method.getName());
   476             if (! (obj instanceof ScriptFunction)) {
   477                 return false;
   478             }
   479         }
   480         return true;
   481     }
   483     // don't make this public!!
   484     static ScriptObject getNashornGlobal() {
   485         return Context.getGlobal();
   486     }
   488     static void setNashornGlobal(final ScriptObject newGlobal) {
   489         AccessController.doPrivileged(new PrivilegedAction<Void>() {
   490             @Override
   491             public Void run() {
   492                Context.setGlobal(newGlobal);
   493                return null;
   494             }
   495         });
   496     }
   497 }

mercurial