/* * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.script.javascript; import com.sun.script.util.*; import javax.script.*; import sun.org.mozilla.javascript.internal.*; import java.lang.reflect.Method; import java.io.*; import java.security.*; import java.util.*; /** * Implementation of <code>ScriptEngine</code> using the Mozilla Rhino * interpreter. * * @author Mike Grogan * @author A. Sundararajan * @since 1.6 */ public final class RhinoScriptEngine extends AbstractScriptEngine implements Invocable, Compilable { private static final boolean DEBUG = false; private AccessControlContext accCtxt; /* Scope where standard JavaScript objects and our * extensions to it are stored. Note that these are not * user defined engine level global variables. These are * variables have to be there on all compliant ECMAScript * scopes. We put these standard objects in this top level. */ private RhinoTopLevel topLevel; /* map used to store indexed properties in engine scope * refer to comment on 'indexedProps' in ExternalScriptable.java. */ private Map<Object, Object> indexedProps; private ScriptEngineFactory factory; private InterfaceImplementor implementor; private static final int languageVersion = getLanguageVersion(); private static final int optimizationLevel = getOptimizationLevel(); static { ContextFactory.initGlobal(new ContextFactory() { /** * Create new Context instance to be associated with the current thread. */ @Override protected Context makeContext() { Context cx = super.makeContext(); cx.setLanguageVersion(languageVersion); cx.setOptimizationLevel(optimizationLevel); cx.setClassShutter(RhinoClassShutter.getInstance()); cx.setWrapFactory(RhinoWrapFactory.getInstance()); return cx; } /** * Execute top call to script or function. When the runtime is about to * execute a script or function that will create the first stack frame * with scriptable code, it calls this method to perform the real call. * In this way execution of any script happens inside this function. */ @Override protected Object doTopCall(final Callable callable, final Context cx, final Scriptable scope, final Scriptable thisObj, final Object[] args) { AccessControlContext accCtxt = null; Scriptable global = ScriptableObject.getTopLevelScope(scope); Scriptable globalProto = global.getPrototype(); if (globalProto instanceof RhinoTopLevel) { accCtxt = ((RhinoTopLevel)globalProto).getAccessContext(); } if (accCtxt != null) { return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return superDoTopCall(callable, cx, scope, thisObj, args); } }, accCtxt); } else { return superDoTopCall(callable, cx, scope, thisObj, args); } } private Object superDoTopCall(Callable callable, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { return super.doTopCall(callable, cx, scope, thisObj, args); } }); } private static final String RHINO_JS_VERSION = "rhino.js.version"; private static int getLanguageVersion() { int version; String tmp = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction(RHINO_JS_VERSION)); if (tmp != null) { version = Integer.parseInt((String)tmp); } else { version = Context.VERSION_1_8; } return version; } private static final String RHINO_OPT_LEVEL = "rhino.opt.level"; private static int getOptimizationLevel() { int optLevel = -1; // disable optimizer under security manager, for now. if (System.getSecurityManager() == null) { optLevel = Integer.getInteger(RHINO_OPT_LEVEL, -1); } return optLevel; } /** * Creates a new instance of RhinoScriptEngine */ public RhinoScriptEngine() { if (System.getSecurityManager() != null) { accCtxt = AccessController.getContext(); } Context cx = enterContext(); try { topLevel = new RhinoTopLevel(cx, this); } finally { cx.exit(); } indexedProps = new HashMap<Object, Object>(); //construct object used to implement getInterface implementor = new InterfaceImplementor(this) { protected boolean isImplemented(Object thiz, Class<?> iface) { Context cx = enterContext(); try { if (thiz != null && !(thiz instanceof Scriptable)) { thiz = cx.toObject(thiz, topLevel); } Scriptable engineScope = getRuntimeScope(context); Scriptable localScope = (thiz != null)? (Scriptable) thiz : engineScope; for (Method method : iface.getMethods()) { // ignore methods of java.lang.Object class if (method.getDeclaringClass() == Object.class) { continue; } Object obj = ScriptableObject.getProperty(localScope, method.getName()); if (! (obj instanceof Function)) { return false; } } return true; } finally { cx.exit(); } } protected Object convertResult(Method method, Object res) throws ScriptException { Class desiredType = method.getReturnType(); if (desiredType == Void.TYPE) { return null; } else { return Context.jsToJava(res, desiredType); } } }; } public Object eval(Reader reader, ScriptContext ctxt) throws ScriptException { Object ret; Context cx = enterContext(); try { Scriptable scope = getRuntimeScope(ctxt); String filename = (String) get(ScriptEngine.FILENAME); filename = filename == null ? "<Unknown source>" : filename; ret = cx.evaluateReader(scope, reader, filename , 1, null); } catch (RhinoException re) { if (DEBUG) re.printStackTrace(); int line = (line = re.lineNumber()) == 0 ? -1 : line; String msg; if (re instanceof JavaScriptException) { msg = String.valueOf(((JavaScriptException)re).getValue()); } else { msg = re.toString(); } ScriptException se = new ScriptException(msg, re.sourceName(), line); se.initCause(re); throw se; } catch (IOException ee) { throw new ScriptException(ee); } finally { cx.exit(); } return unwrapReturnValue(ret); } public Object eval(String script, ScriptContext ctxt) throws ScriptException { if (script == null) { throw new NullPointerException("null script"); } return eval(new StringReader(script) , ctxt); } public ScriptEngineFactory getFactory() { if (factory != null) { return factory; } else { return new RhinoScriptEngineFactory(); } } public Bindings createBindings() { return new SimpleBindings(); } //Invocable methods public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException { return invoke(null, name, args); } public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException { if (thiz == null) { throw new IllegalArgumentException("script object can not be null"); } return invoke(thiz, name, args); } private Object invoke(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException { Context cx = enterContext(); try { if (name == null) { throw new NullPointerException("method name is null"); } if (thiz != null && !(thiz instanceof Scriptable)) { thiz = cx.toObject(thiz, topLevel); } Scriptable engineScope = getRuntimeScope(context); Scriptable localScope = (thiz != null)? (Scriptable) thiz : engineScope; Object obj = ScriptableObject.getProperty(localScope, name); if (! (obj instanceof Function)) { throw new NoSuchMethodException("no such method: " + name); } Function func = (Function) obj; Scriptable scope = func.getParentScope(); if (scope == null) { scope = engineScope; } Object result = func.call(cx, scope, localScope, wrapArguments(args)); return unwrapReturnValue(result); } catch (RhinoException re) { if (DEBUG) re.printStackTrace(); int line = (line = re.lineNumber()) == 0 ? -1 : line; ScriptException se = new ScriptException(re.toString(), re.sourceName(), line); se.initCause(re); throw se; } finally { cx.exit(); } } public <T> T getInterface(Class<T> clasz) { try { return implementor.getInterface(null, clasz); } catch (ScriptException e) { return null; } } public <T> T getInterface(Object thiz, Class<T> clasz) { if (thiz == null) { throw new IllegalArgumentException("script object can not be null"); } try { return implementor.getInterface(thiz, clasz); } catch (ScriptException e) { return null; } } private static final String printSource = "function print(str, newline) { \n" + " if (typeof(str) == 'undefined') { \n" + " str = 'undefined'; \n" + " } else if (str == null) { \n" + " str = 'null'; \n" + " } \n" + " var out = context.getWriter(); \n" + " if (!(out instanceof java.io.PrintWriter))\n" + " out = new java.io.PrintWriter(out); \n" + " out.print(String(str)); \n" + " if (newline) out.print('\\n'); \n" + " out.flush(); \n" + "}\n" + "function println(str) { \n" + " print(str, true); \n" + "}"; Scriptable getRuntimeScope(ScriptContext ctxt) { if (ctxt == null) { throw new NullPointerException("null script context"); } // we create a scope for the given ScriptContext Scriptable newScope = new ExternalScriptable(ctxt, indexedProps); // Set the prototype of newScope to be 'topLevel' so that // JavaScript standard objects are visible from the scope. newScope.setPrototype(topLevel); // define "context" variable in the new scope newScope.put("context", newScope, ctxt); // define "print", "println" functions in the new scope Context cx = enterContext(); try { cx.evaluateString(newScope, printSource, "print", 1, null); } finally { cx.exit(); } return newScope; } //Compilable methods public CompiledScript compile(String script) throws ScriptException { return compile(new StringReader(script)); } public CompiledScript compile(java.io.Reader script) throws ScriptException { CompiledScript ret = null; Context cx = enterContext(); try { String fileName = (String) get(ScriptEngine.FILENAME); if (fileName == null) { fileName = "<Unknown Source>"; } Scriptable scope = getRuntimeScope(context); Script scr = cx.compileReader(scope, script, fileName, 1, null); ret = new RhinoCompiledScript(this, scr); } catch (Exception e) { if (DEBUG) e.printStackTrace(); throw new ScriptException(e); } finally { cx.exit(); } return ret; } //package-private helpers static Context enterContext() { // call this always so that initializer of this class runs // and initializes custom wrap factory and class shutter. return Context.enter(); } void setEngineFactory(ScriptEngineFactory fac) { factory = fac; } AccessControlContext getAccessContext() { return accCtxt; } Object[] wrapArguments(Object[] args) { if (args == null) { return Context.emptyArgs; } Object[] res = new Object[args.length]; for (int i = 0; i < res.length; i++) { res[i] = Context.javaToJS(args[i], topLevel); } return res; } Object unwrapReturnValue(Object result) { if (result instanceof Wrapper) { result = ( (Wrapper) result).unwrap(); } return result instanceof Undefined ? null : result; } /* public static void main(String[] args) throws Exception { if (args.length == 0) { System.out.println("No file specified"); return; } InputStreamReader r = new InputStreamReader(new FileInputStream(args[0])); ScriptEngine engine = new RhinoScriptEngine(); engine.put("x", "y"); engine.put(ScriptEngine.FILENAME, args[0]); engine.eval(r); System.out.println(engine.get("x")); } */ }