All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.htmlunit.corejs.javascript.engine.RhinoScriptEngine Maven / Gradle / Ivy

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.htmlunit.corejs.javascript.engine;

import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import javax.script.AbstractScriptEngine;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import org.htmlunit.corejs.javascript.Callable;
import org.htmlunit.corejs.javascript.Context;
import org.htmlunit.corejs.javascript.ContextFactory;
import org.htmlunit.corejs.javascript.RhinoException;
import org.htmlunit.corejs.javascript.Script;
import org.htmlunit.corejs.javascript.Scriptable;
import org.htmlunit.corejs.javascript.ScriptableObject;

/**
 * This is the implementation of the standard ScriptEngine interface for Rhino.
 *
 * 

An instance of the Rhino ScriptEngine is fully self-contained. Bindings at the GLOBAL_SCOPE * may be set, but there is nothing special about them -- if both global and ENGINE_SCOPE bindings * are set then the "engine" bindings override the global ones. * *

The Rhino engine is not thread safe. Rhino does no synchronization of ScriptEngine instances * and no synchronization of Bindings instances. It is up to the caller to ensure that the * ScriptEngine and all its Bindings are used by a single thread at a time. * *

The Rhino script engine includes some top-level built-in functions. See the Builtins class for * more documentation. * *

The engine supports a few configuration parameters that may be set at the "engine scope". Both * are numbers that may be set to a String or Number object. * *

    *
  • javax.script.language_version: The version of the JavaScript language supported, which is * an integer defined in the Context class. The default is the latest "ES6" version, defined * as 200. *
  • org.htmlunit.corejs.javascript.optimization_level: The level of optimization Rhino performs on the * generated bytecode. Default is 9, which is the most. Set to -1 to use interpreted mode. *
*/ public class RhinoScriptEngine extends AbstractScriptEngine implements Compilable, Invocable { /** * Reserved key for the Rhino optimization level. Default is "9," for optimized and compiled * code. Set this to "-1" to run Rhino in interpreted mode -- this is much much slower but the * only option on platforms like Android that don't support class files. */ public static final String OPTIMIZATION_LEVEL = "org.htmlunit.corejs.javascript.optimization_level"; static final int DEFAULT_LANGUAGE_VERSION = Context.VERSION_ES6; private static final int DEFAULT_OPT = 9; private static final boolean DEFAULT_DEBUG = true; private static final String DEFAULT_FILENAME = "eval"; private static final CtxFactory ctxFactory = new CtxFactory(); private final RhinoScriptEngineFactory factory; private final Builtins builtins; private ScriptableObject topLevelScope = null; RhinoScriptEngine(RhinoScriptEngineFactory factory) { this.factory = factory; this.builtins = new Builtins(); } private Scriptable initScope(Context cx, ScriptContext sc) throws ScriptException { configureContext(cx); if (topLevelScope == null) { topLevelScope = cx.initStandardObjects(); // We need to stash this away so that the built in functions can find // this engine's specific stuff that they need to work. topLevelScope.associateValue(Builtins.BUILTIN_KEY, builtins); builtins.register(cx, topLevelScope, sc); } Scriptable engineScope = new BindingsObject(sc.getBindings(ScriptContext.ENGINE_SCOPE)); engineScope.setParentScope(null); engineScope.setPrototype(topLevelScope); if (sc.getBindings(ScriptContext.GLOBAL_SCOPE) != null) { Scriptable globalScope = new BindingsObject(sc.getBindings(ScriptContext.GLOBAL_SCOPE)); globalScope.setParentScope(null); globalScope.setPrototype(topLevelScope); engineScope.setPrototype(globalScope); } return engineScope; } @Override public Object eval(String script, ScriptContext context) throws ScriptException { try (Context cx = ctxFactory.enterContext()) { Scriptable scope = initScope(cx, context); Object ret = cx.evaluateString(scope, script, getFilename(), 0, null); return Context.jsToJava(ret, Object.class); } catch (RhinoException re) { throw new ScriptException( re.getMessage(), re.sourceName(), re.lineNumber(), re.columnNumber()); } } @Override public Object eval(Reader reader, ScriptContext context) throws ScriptException { try (Context cx = ctxFactory.enterContext()) { Scriptable scope = initScope(cx, context); Object ret = cx.evaluateReader(scope, reader, getFilename(), 0, null); return Context.jsToJava(ret, Object.class); } catch (RhinoException re) { throw new ScriptException( re.getMessage(), re.sourceName(), re.lineNumber(), re.columnNumber()); } catch (IOException ioe) { throw new ScriptException(ioe); } } @Override public CompiledScript compile(String script) throws ScriptException { try (Context cx = ctxFactory.enterContext()) { configureContext(cx); Script s = cx.compileString(script, getFilename(), 1, null); return new RhinoCompiledScript(this, s); } catch (RhinoException re) { throw new ScriptException( re.getMessage(), re.sourceName(), re.lineNumber(), re.columnNumber()); } } @Override public CompiledScript compile(Reader script) throws ScriptException { try (Context cx = ctxFactory.enterContext()) { configureContext(cx); Script s = cx.compileReader(script, getFilename(), 1, null); return new RhinoCompiledScript(this, s); } catch (RhinoException re) { throw new ScriptException( re.getMessage(), re.sourceName(), re.lineNumber(), re.columnNumber()); } catch (IOException ioe) { throw new ScriptException(ioe); } } Object eval(Script script, ScriptContext sc) throws ScriptException { try (Context cx = ctxFactory.enterContext()) { Scriptable scope = initScope(cx, sc); Object ret = script.exec(cx, scope); return Context.jsToJava(ret, Object.class); } catch (RhinoException re) { throw new ScriptException( re.getMessage(), re.sourceName(), re.lineNumber(), re.columnNumber()); } } @Override public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException { return invokeMethod(null, name, args); } @Override public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException { return invokeMethodRaw(thiz, name, Object.class, args); } Object invokeMethodRaw(Object thiz, String name, Class returnType, Object... args) throws ScriptException, NoSuchMethodException { try (Context cx = ctxFactory.enterContext()) { Scriptable scope = initScope(cx, context); Scriptable localThis; if (thiz == null) { localThis = scope; } else { localThis = Context.toObject(thiz, scope); } Object f = ScriptableObject.getProperty(localThis, name); if (f == Scriptable.NOT_FOUND) { throw new NoSuchMethodException(name); } if (!(f instanceof Callable)) { throw new ScriptException("\"" + name + "\" is not a function"); } Callable func = (Callable) f; if (args != null) { for (int i = 0; i < args.length; i++) { args[i] = Context.javaToJS(args[i], scope); } } Object ret = func.call(cx, scope, localThis, args); if (returnType == Void.TYPE) { return null; } return Context.jsToJava(ret, returnType); } catch (RhinoException re) { throw new ScriptException( re.getMessage(), re.sourceName(), re.lineNumber(), re.columnNumber()); } } @SuppressWarnings("unchecked") @Override public T getInterface(Class clasz) { if ((clasz == null) || !clasz.isInterface()) { throw new IllegalArgumentException("Not an interface"); } try (Context cx = ctxFactory.enterContext()) { Scriptable scope = initScope(cx, context); if (methodsMissing(scope, clasz)) { return null; } } catch (ScriptException se) { return null; } return (T) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), new Class[] {clasz}, new RhinoInvocationHandler(this, null)); } @SuppressWarnings("unchecked") @Override public T getInterface(Object thiz, Class clasz) { if ((clasz == null) || !clasz.isInterface()) { throw new IllegalArgumentException("Not an interface"); } try (Context cx = ctxFactory.enterContext()) { Scriptable scope = initScope(cx, context); Scriptable thisObj = Context.toObject(thiz, scope); if (methodsMissing(thisObj, clasz)) { return null; } } catch (ScriptException se) { return null; } return (T) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), new Class[] {clasz}, new RhinoInvocationHandler(this, thiz)); } @Override public Bindings createBindings() { return new SimpleBindings(); } @Override public ScriptEngineFactory getFactory() { return factory; } private void configureContext(Context cx) throws ScriptException { Object lv = get(ScriptEngine.LANGUAGE_VERSION); if (lv != null) { cx.setLanguageVersion(parseInteger(lv)); } Object ol = get(OPTIMIZATION_LEVEL); if (ol != null) { cx.setOptimizationLevel(parseInteger(ol)); } } private static int parseInteger(Object v) throws ScriptException { if (v instanceof String) { try { return Integer.parseInt((String) v); } catch (NumberFormatException nfe) { throw new ScriptException("Invalid number " + v); } } else if (v instanceof Integer) { return ((Integer) v).intValue(); } else { throw new ScriptException("Value must be a string or number"); } } private String getFilename() { Object fn = get(ScriptEngine.FILENAME); if (fn instanceof String) { return (String) fn; } return DEFAULT_FILENAME; } private static boolean methodsMissing(Scriptable scope, Class clasz) { for (Method m : clasz.getMethods()) { if (m.getDeclaringClass() == Object.class) { continue; } Object methodObj = ScriptableObject.getProperty(scope, m.getName()); if (!(methodObj instanceof Callable)) { return true; } } return false; } private static final class CtxFactory extends ContextFactory { @Override protected boolean hasFeature(Context cx, int featureIndex) { if (featureIndex == Context.FEATURE_INTEGER_WITHOUT_DECIMAL_PLACE) { return true; } return super.hasFeature(cx, featureIndex); } @Override protected void onContextCreated(Context cx) { cx.setLanguageVersion(Context.VERSION_ES6); cx.setOptimizationLevel(DEFAULT_OPT); cx.setGeneratingDebug(DEFAULT_DEBUG); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy