Implement standard Java ScriptEngine

This is not based on the now-removed JDK code but instead does a
few things in a more modern way. See the comments for supported
parameters (you can set language and optimization level via
properties) and built-in functions (only print is supported right now.)

This is built into a separate JAR called "rhino-engine" because
otherwise, including it in any Java 8 JDK would break scripts that
are expecting to see Nashorn instead.
This commit is contained in:
Gregory Brail 2020-06-05 14:38:28 -07:00 коммит произвёл Greg Brail
Родитель cc8160287e
Коммит d120651524
13 изменённых файлов: 1305 добавлений и 8 удалений

Просмотреть файл

@ -28,7 +28,7 @@ jobs:
- ~/.gradle
key: v1-dependencies-{{ checksum "build.gradle" }}
- run: ./gradlew check jar --max-workers=4
- run: ./gradlew check jar --max-workers=2
- store_test_results:
path: buildGradle/test-results

Просмотреть файл

@ -112,9 +112,8 @@ idea {
task runtimeJar(type: Jar) {
dependsOn compileJava
archiveBaseName = 'rhino-runtime'
from (sourceSets.main.output) {
exclude 'org/mozilla/javascript/tools'
}
from sourceSets.main.output
excludes = ["org/mozilla/javascript/tools", "org/mozilla/javascript/engine/**", "META-INF/services/**"]
manifest {
attributes(
"Manifest-Version": "1.0",
@ -132,9 +131,30 @@ task runtimeJar(type: Jar) {
}
}
task engineJar(type: Jar) {
dependsOn compileJava
archiveBaseName = 'rhino-engine'
from (sourceSets.main.output) {
include 'org/mozilla/javascript/engine/**'
include 'META-INF/services/**'
}
manifest {
attributes(
"Manifest-Version": "1.0",
"Implementation-Version": project.version,
"Implementation-Title": "Mozilla Rhino ScriptEngine",
"Implementation-Vendor": "Mozilla Foundation",
"Implementation-URL": "http://www.mozilla.org/rhino",
"Built-Date": new Date().format("yyyy-MM-dd"),
"Built-Time": new Date().format("HH:mm:ss"),
)
}
}
jar {
dependsOn runtimeJar
dependsOn runtimeJar, engineJar
from "LICENSE.txt"
excludes = ["org/mozilla/javascript/engine/**", "META-INF/services/**"]
manifest {
attributes(
"Manifest-Version": "1.0",
@ -167,7 +187,13 @@ task javadocJar(type: Jar) {
task runtimeJavadocJar(type: Jar) {
classifier = 'javadoc'
from javadoc
exclude 'org/mozilla/javascript/tools'
exclude 'org/mozilla/javascript/tools', 'org/mozilla/javascript/engine'
}
task engineJavadocJar(type: Jar) {
classifier = 'javadoc'
from javadoc
include 'org/mozilla/javascript/engine/**'
}
task sourceJar(type: Jar) {
@ -177,7 +203,13 @@ task sourceJar(type: Jar) {
task runtimeSourceJar(type: Jar) {
classifier 'sources'
from sourceSets.main.allJava
exclude 'org/mozilla/javascript/tools'
exclude 'org/mozilla/javascript/tools', 'org/mozilla/javascript/engine'
}
task engineSourceJar(type: Jar) {
classifier 'sources'
from sourceSets.main.allJava
include 'org/mozilla/javascript/engine/**'
}
publishing {
@ -257,6 +289,46 @@ publishing {
}
}
rhinoengine(MavenPublication) {
groupId 'org.mozilla'
artifactId 'rhino-engine'
artifacts = [engineJar, engineSourceJar, engineJavadocJar]
pom.withXml {
def root = asNode()
root.appendNode('description', """
Rhino is an open-source implementation of JavaScript written entirely in Java.
It is typically embedded into Java applications to provide scripting to end users.
""")
root.appendNode("url", "https://developer.mozilla.org/en/Rhino")
def p = root.appendNode("parent")
p.appendNode("groupId", "org.sonatype.oss")
p.appendNode("artifactId", "oss-parent")
p.appendNode("version", "7")
def l = root.appendNode("licenses").appendNode("license")
l.appendNode("name", "Mozilla Public License, Version 2.0")
l.appendNode("url", "http://www.mozilla.org/MPL/2.0/index.txt")
def scm = root.appendNode("scm")
scm.appendNode("connection", "scm:git:git@github.com:mozilla/rhino.git")
scm.appendNode("developerConnection", "scm:git:git@github.com:mozilla/rhino.git")
scm.appendNode("url", "git@github.com:mozilla/rhino.git")
def o = root.appendNode("organization")
o.appendNode("name", "The Mozilla Foundation")
o.appendNode("url", "http://www.mozilla.org")
def deps = root.appendNode("dependencies")
def rhino = deps.appendNode("dependency")
rhino.appendNode("groupId", "org.mozilla")
rhino.appendNode("artifactId", "rhino")
rhino.appendNode("version", getVersion())
}
}
}
if (project.hasProperty("mavenPassword")) {
@ -291,7 +363,7 @@ jacocoTestReport {
checkstyle {
configFile = file("${projectDir}/checkstyle.xml")
sourceSets = [project.sourceSets.main]
sourceSets = [ project.sourceSets.main ]
}
distributions {

Просмотреть файл

@ -0,0 +1 @@
org.mozilla.javascript.engine.RhinoScriptEngineFactory

Просмотреть файл

@ -0,0 +1,60 @@
/* 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.mozilla.javascript.engine;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import javax.script.Bindings;
/**
* This class makes the Bindings object into a Scriptable. That way, we can query and modify
* the contents of the Bindings on demand.
*/
public class BindingsObject
extends ScriptableObject {
private final Bindings bindings;
BindingsObject(Bindings bindings) {
if (bindings == null) {
throw new IllegalArgumentException("Bindings must not be null");
}
this.bindings = bindings;
}
@Override
public String getClassName() {
return "BindingsObject";
}
@Override
public Object get(String name, Scriptable start) {
Object ret = bindings.get(name);
if (ret == null) {
return Scriptable.NOT_FOUND;
}
return Context.jsToJava(ret, Object.class);
}
@Override
public void put(String name, Scriptable start, Object value) {
bindings.put(name, Context.javaToJS(value, start));
}
@Override
public void delete(String name) {
bindings.remove(name);
}
@Override
public boolean has(String name, Scriptable start) {
return bindings.containsKey(name);
}
@Override
public Object[] getIds() {
return bindings.keySet().toArray();
}
}

Просмотреть файл

@ -0,0 +1,59 @@
/* 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.mozilla.javascript.engine;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import javax.script.ScriptContext;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
/**
* <p>
* This class defines the following built-in functions for the RhinoScriptEngine.
* </p>
* <ul>
* <li>print(arg, arg, ...): Write each argument, concatenated to the ScriptEngine's
* "standard output" as a string.</li>
* </ul>
*/
public class Builtins {
static final Object BUILTIN_KEY = new Object();
private Writer stdout;
void register(Context cx, ScriptableObject scope, ScriptContext sc) {
if (sc.getWriter() == null) {
stdout = new OutputStreamWriter(System.out);
} else {
stdout = sc.getWriter();
}
scope.defineFunctionProperties(new String[]{"print"},
Builtins.class,
ScriptableObject.PERMANENT | ScriptableObject.DONTENUM);
}
public static void print(Context cx, Scriptable thisObj, Object[] args, Function f)
throws IOException {
Builtins self = getSelf(thisObj);
for (Object arg : args) {
self.stdout.write(ScriptRuntime.toString(arg));
}
self.stdout.write('\n');
}
private static Builtins getSelf(Scriptable scope) {
// Since this class is invoked as a set of anonymous functions, "this"
// in JavaScript does not point to "this" in Java. We set a key on the
// top-level scope to address this.
return (Builtins) ScriptableObject.getTopScopeValue(scope, BUILTIN_KEY);
}
}

Просмотреть файл

@ -0,0 +1,33 @@
/* 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.mozilla.javascript.engine;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import org.mozilla.javascript.Script;
public class RhinoCompiledScript
extends CompiledScript {
private final RhinoScriptEngine engine;
private final Script script;
RhinoCompiledScript(RhinoScriptEngine engine, Script script) {
this.engine = engine;
this.script = script;
}
@Override
public Object eval(ScriptContext context) throws ScriptException {
return engine.eval(script, context);
}
@Override
public ScriptEngine getEngine() {
return engine;
}
}

Просмотреть файл

@ -0,0 +1,25 @@
/* 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.mozilla.javascript.engine;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class RhinoInvocationHandler
implements InvocationHandler {
private final Object thiz;
private final RhinoScriptEngine engine;
RhinoInvocationHandler(RhinoScriptEngine engine, Object thiz) {
this.engine = engine;
this.thiz = thiz;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return engine.invokeMethodRaw(thiz, method.getName(), method.getReturnType(), args);
}
}

Просмотреть файл

@ -0,0 +1,363 @@
/* 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.mozilla.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.mozilla.javascript.Callable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
/**
* <p>
* This is the implementation of the standard ScriptEngine interface for Rhino.
* </p>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* The Rhino script engine includes some top-level built-in functions. See the Builtins class for
* more documentation.
* </p>
* <p>
* 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.
* </p>
* <ul>
* <li>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.</li>
* <li>org.mozilla.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.</li>
* </ul>
*/
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.mozilla.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 {
Context cx = ctxFactory.enterContext();
try {
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());
} finally {
Context.exit();
}
}
@Override
public Object eval(Reader reader, ScriptContext context) throws ScriptException {
Context cx = ctxFactory.enterContext();
try {
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);
} finally {
Context.exit();
}
}
@Override
public CompiledScript compile(String script) throws ScriptException {
Context cx = ctxFactory.enterContext();
try {
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());
} finally {
Context.exit();
}
}
@Override
public CompiledScript compile(Reader script) throws ScriptException {
Context cx = ctxFactory.enterContext();
try {
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);
} finally {
Context.exit();
}
}
Object eval(Script script, ScriptContext sc) throws ScriptException {
Context cx = ctxFactory.enterContext();
try {
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());
} finally {
Context.exit();
}
}
@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 {
Context cx = ctxFactory.enterContext();
try {
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());
} finally {
Context.exit();
}
}
@Override
public <T> T getInterface(Class<T> clasz) {
if ((clasz == null) || !clasz.isInterface()) {
throw new IllegalArgumentException("Not an interface");
}
Context cx = ctxFactory.enterContext();
try {
Scriptable scope = initScope(cx, context);
if (methodsMissing(scope, clasz)) {
return null;
}
} catch (ScriptException se) {
return null;
} finally {
Context.exit();
}
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{clasz}, new RhinoInvocationHandler(this, null));
}
@Override
public <T> T getInterface(Object thiz, Class<T> clasz) {
if ((clasz == null) || !clasz.isInterface()) {
throw new IllegalArgumentException("Not an interface");
}
Context cx = ctxFactory.enterContext();
try {
Scriptable scope = initScope(cx, context);
Scriptable thisObj = Context.toObject(thiz, scope);
if (methodsMissing(thisObj, clasz)) {
return null;
}
} catch (ScriptException se) {
return null;
} finally {
Context.exit();
}
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 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;
} 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 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);
}
}
}

Просмотреть файл

@ -0,0 +1,140 @@
/* 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.mozilla.javascript.engine;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import org.mozilla.javascript.Context;
/**
* <p>
* This is an implementation of the standard Java "ScriptEngine" for Rhino. If the Rhino engine
* (typically in the form of the "rhino-engine" JAR) is in the classpath, then this script
* engine will be activated.
* </p>
* <p>
* See the list of constants in this class for the list of language names, file extensions, and
* MIME types that this engine supports. This list is essentially the same as the list supported
* in the Nashorn script engine that was included in Java 8.
* </p>
* <p>
* Since this engine and Nashorn support the same language and file extensions, then unless
* you are sure you are running in an environment that has Nashorn, the best way to get this
* engine is to call ScriptEngine.getEngineByName("rhino") to ask for Rhino directly.
* </p>
*/
public class RhinoScriptEngineFactory
implements ScriptEngineFactory {
public static final String NAME = "rhino";
public static final String LANGUAGE = "javascript";
public static final List<String> NAMES =
Arrays.asList("rhino", "Rhino", "javascript", "JavaScript");
public static final List<String> EXTENSIONS =
Collections.singletonList("js");
public static final List<String> MIME_TYPES =
Arrays.asList("application/javascript", "application/ecmascript",
"text/javascript", "text/ecmascript");
public static final String LANGUAGE_VERSION =
String.valueOf(RhinoScriptEngine.DEFAULT_LANGUAGE_VERSION);
@Override
public String getEngineName() {
return NAME;
}
@Override
public String getEngineVersion() {
Context cx = Context.enter();
try {
String v = cx.getImplementationVersion();
return (v == null ? "unknown" : v);
} finally {
Context.exit();
}
}
@Override
public List<String> getExtensions() {
return EXTENSIONS;
}
@Override
public List<String> getMimeTypes() {
return MIME_TYPES;
}
@Override
public List<String> getNames() {
return NAMES;
}
@Override
public String getLanguageName() {
return LANGUAGE;
}
@Override
public String getLanguageVersion() {
return LANGUAGE_VERSION;
}
@Override
public Object getParameter(String key) {
switch (key) {
case ScriptEngine.ENGINE:
return getEngineName();
case ScriptEngine.ENGINE_VERSION:
return getEngineVersion();
case ScriptEngine.LANGUAGE:
return getLanguageName();
case ScriptEngine.LANGUAGE_VERSION:
return getLanguageVersion();
case ScriptEngine.NAME:
return NAME;
case "THREADING":
// Engines are explicitly not thread-safe
return null;
default:
return null;
}
}
@Override
public String getMethodCallSyntax(String obj, String m, String... args) {
StringBuilder sb = new StringBuilder();
sb.append(obj).append('.').append(m).append('(');
for (int i = 0; i < args.length; i++) {
if (i > 0) {
sb.append(',');
}
sb.append(args[i]);
}
sb.append(");");
return sb.toString();
}
@Override
public String getOutputStatement(String toDisplay) {
return "print('" + toDisplay + "');";
}
@Override
public String getProgram(String... statements) {
StringBuilder sb = new StringBuilder();
for (String stmt : statements) {
sb.append(stmt).append(";\n");
}
return sb.toString();
}
@Override
public ScriptEngine getScriptEngine() {
return new RhinoScriptEngine(this);
}
}

Просмотреть файл

@ -0,0 +1,54 @@
package org.mozilla.javascript.tests.scriptengine;
import java.io.StringWriter;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mozilla.javascript.engine.RhinoScriptEngineFactory;
import static org.junit.Assert.*;
public class BuiltinsTest {
private static ScriptEngineManager manager;
private ScriptEngine engine;
@BeforeClass
public static void init() {
manager = new ScriptEngineManager();
manager.registerEngineName("rhino", new RhinoScriptEngineFactory());
}
@Before
public void setup() {
engine = manager.getEngineByName("rhino");
}
@Test
public void testPrintStdout() throws ScriptException {
engine.eval("print('Hello, World!');");
}
@Test
public void testPrintWriter() throws ScriptException {
StringWriter sw = new StringWriter();
ScriptContext sc = new SimpleScriptContext();
sc.setWriter(sw);
engine.eval("print('one', 2, true);", sc);
assertEquals(sw.toString(), "one2true\n");
}
@Test
public void testPrintWriterGeneric() throws ScriptException {
StringWriter sw = new StringWriter();
engine.getContext().setWriter(sw);
engine.eval(engine.getFactory().getOutputStatement("Display This!"));
assertEquals(sw.toString(), "Display This!\n");
}
}

Просмотреть файл

@ -0,0 +1,56 @@
package org.mozilla.javascript.tests.scriptengine;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import org.junit.Test;
import org.mozilla.javascript.engine.RhinoScriptEngine;
import org.mozilla.javascript.engine.RhinoScriptEngineFactory;
import static org.junit.Assert.*;
/*
* A series of tests that depend on us having our engine registered with the
* ScriptEngineManager by default.
*/
public class FactoryTest {
@Test
public void findRhinoFactory() {
ScriptEngineManager manager = new ScriptEngineManager();
for (ScriptEngineFactory factory : manager.getEngineFactories()) {
if (factory instanceof RhinoScriptEngineFactory) {
assertEquals("rhino", factory.getEngineName());
assertEquals("rhino", factory.getParameter(ScriptEngine.ENGINE));
assertEquals("rhino", factory.getParameter(ScriptEngine.NAME));
// This could be "unknown" if we're not running from a regular JAR
assertFalse(factory.getEngineVersion().isEmpty());
assertEquals("javascript", factory.getLanguageName());
assertEquals("javascript", factory.getParameter(ScriptEngine.LANGUAGE));
assertEquals("200", factory.getLanguageVersion());
assertEquals("200", factory.getParameter(ScriptEngine.LANGUAGE_VERSION));
assertNull(factory.getParameter("THREADING"));
assertTrue(factory.getExtensions().contains("js"));
assertTrue(factory.getMimeTypes().contains("application/javascript"));
assertTrue(factory.getMimeTypes().contains("application/ecmascript"));
assertTrue(factory.getMimeTypes().contains("text/javascript"));
assertTrue(factory.getMimeTypes().contains("text/ecmascript"));
assertTrue(factory.getNames().contains("rhino"));
assertTrue(factory.getNames().contains("Rhino"));
assertTrue(factory.getNames().contains("javascript"));
assertTrue(factory.getNames().contains("JavaScript"));
return;
}
}
fail("Expected to find Rhino script engine");
}
@Test
public void testRhinoFactory() {
// This will always uniquely return our engine.
// In Java 8, other ways to find it may return Nashorn.
ScriptEngine engine = new ScriptEngineManager().getEngineByName("rhino");
assertTrue(engine instanceof RhinoScriptEngine);
}
}

Просмотреть файл

@ -0,0 +1,158 @@
package org.mozilla.javascript.tests.scriptengine;
import java.io.FileNotFoundException;
import java.io.FileReader;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mozilla.javascript.engine.RhinoScriptEngineFactory;
import static org.junit.Assert.*;
public class InvocableTest {
private static ScriptEngineManager manager;
private ScriptEngine engine;
private Invocable iEngine;
@BeforeClass
public static void init() {
manager = new ScriptEngineManager();
manager.registerEngineName("rhino", new RhinoScriptEngineFactory());
}
@Before
public void setup() {
engine = manager.getEngineByName("rhino");
iEngine = (Invocable) engine;
}
@Test
public void invokeFunctionTest() throws ScriptException, NoSuchMethodException {
engine.eval("function foo(a, b) { return a + b; }");
Object result = iEngine.invokeFunction("foo", 2, 2);
assertEquals(result, 4L);
}
@Test
public void invokeScriptFunctionTest() throws ScriptException, NoSuchMethodException {
Object scriptObj = engine.eval("let o = {};\n"
+ "o.test = function(x) { return x + 2; }\n"
+ "o;");
assertEquals(4L, iEngine.invokeMethod(scriptObj, "test", 2));
}
@Test
public void invokeGenericFunctionTest() throws ScriptException, NoSuchMethodException {
engine.eval("let o = {};\n"
+ "o.test = function(x) { return x + 2; }\n");
Object result = engine.eval(engine.getFactory().getMethodCallSyntax("o", "test", "1"));
assertEquals(3L, result);
}
@Test
public void invokeGenericFunctionTest2() throws ScriptException, NoSuchMethodException {
engine.eval("let o = {};\n"
+ "o.test = function(x, y) { return x + y; }\n");
Object result = engine.eval(engine.getFactory().getMethodCallSyntax("o", "test", "1", "7"));
assertEquals(8L, result);
}
@Test
public void invokeMethodTest()
throws ScriptException, NoSuchMethodException, FileNotFoundException {
engine.eval(new FileReader("testsrc/assert.js"));
engine.eval("function FooObj() { this.x = 0; }\n"
+ "FooObj.prototype.set = function(a, b) { this.x = a + b; }");
engine.eval("let f = new FooObj();\n"
+ "assertEquals(f.x, 0);\n"
+ "f.set(2, 2);\n"
+ "assertEquals(f.x, 4);");
Object fooObj = engine.eval("let y = new FooObj(); y");
assertNotNull(fooObj);
iEngine.invokeMethod(fooObj, "set", 3, 3);
Object result = engine.eval("y.x");
assertEquals(result, 6L);
}
@Test
public void interfaceFunctionTest()
throws ScriptException, FileNotFoundException {
engine.eval(new FileReader("testsrc/assert.js"));
engine.eval("var foo = 'initialized';\n"
+ "function setFoo(v) { foo = v; }\n"
+ "function getFoo() { return foo; }\n"
+ "function addItUp(a, b) { return a + b; }");
I tester = iEngine.getInterface(I.class);
assertEquals(tester.getFoo(), "initialized");
tester.setFoo("tested");
assertEquals(tester.getFoo(), "tested");
assertEquals(tester.addItUp(100, 1), 101);
}
@Test
public void interfaceMethodTest()
throws ScriptException, FileNotFoundException {
engine.eval(new FileReader("testsrc/assert.js"));
Object foo = engine.eval("function Foo() { this.foo = 'initialized' }\n"
+ "Foo.prototype.setFoo = function(v) { this.foo = v; };\n"
+ "Foo.prototype.getFoo = function() { return this.foo; };\n"
+ "Foo.prototype.addItUp = function(a, b) { return a + b; };\n"
+ "new Foo();");
I tester = iEngine.getInterface(foo, I.class);
assertEquals(tester.getFoo(), "initialized");
tester.setFoo("tested");
assertEquals(tester.getFoo(), "tested");
assertEquals(tester.addItUp(100, 1), 101);
}
@Test
public void interfaceFunctionMissingTest() {
I tester = iEngine.getInterface(I.class);
assertNull(tester);
}
@Test
public void interfaceMethodMissingTest()
throws ScriptException {
// Functions defined, but not on the right object
Object foo = engine.eval("var foo = 'initialized';\n"
+ "function setFoo(v) { foo = v; }\n"
+ "function getFoo() { return foo; }\n"
+ "function addItUp(a, b) { return a + b; }\n"
+ "function Foo() {}\n"
+ "new Foo();");
I tester = iEngine.getInterface(foo, I.class);
assertNull(tester);
}
@Test
public void invokeNotFoundTest() {
assertThrows(NoSuchMethodException.class, () -> {
iEngine.invokeFunction("foo", 2, 2);
});
}
@Test
public void invokeNotFunctionTest() {
assertThrows(ScriptException.class, () -> {
engine.eval("foo = 'bar';");
iEngine.invokeFunction("foo", 2, 2);
});
}
interface I {
void setFoo(String v);
String getFoo();
int addItUp(int a, int b);
}
}

Просмотреть файл

@ -0,0 +1,276 @@
package org.mozilla.javascript.tests.scriptengine;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import javax.script.SimpleScriptContext;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mozilla.javascript.engine.RhinoScriptEngine;
import org.mozilla.javascript.engine.RhinoScriptEngineFactory;
import static org.junit.Assert.*;
public class ScriptEngineTest {
private static ScriptEngineManager manager;
private ScriptEngine engine;
private Compilable cEngine;
@BeforeClass
public static void initManager() {
manager = new ScriptEngineManager();
manager.registerEngineName("rhino", new RhinoScriptEngineFactory());
}
@Before
public void init() {
engine = manager.getEngineByName("rhino");
cEngine = (Compilable) engine;
}
@Test
public void testHello() throws ScriptException {
Object result = engine.eval("'Hello, World!';");
assertEquals(result, "Hello, World!");
}
@Test
public void testHelloInterpreted() throws ScriptException {
engine.put(RhinoScriptEngine.OPTIMIZATION_LEVEL, -1);
Object result = engine.eval("'Hello, World!';");
assertEquals(result, "Hello, World!");
}
@Test
public void testHelloReader() throws ScriptException {
String src = "1 + 1;";
StringReader sr = new StringReader(src);
Object result = engine.eval(sr);
assertEquals(result, 2L);
}
@Test
public void testGenericStatements() throws ScriptException {
Object result = engine.eval(engine.getFactory().getProgram(
"let x = 1;",
"let y = 2",
"x + y"
));
assertEquals(3L, result);
}
@Test
public void testThrows() {
assertThrows(ScriptException.class, () -> {
engine.eval("throw 'This is an error'");
});
}
@Test
public void testEngineBindings() throws IOException, ScriptException {
engine.put("string", "Hello");
engine.put("integer", 123);
engine.put("a", "a");
engine.put("b", "b");
engine.put("c", "c");
// Ensure that stuff we just stuck in bindings made it to a global
engine.eval(new FileReader("testsrc/assert.js"));
engine.eval("assertEquals(string, 'Hello');\n"
+ "assertEquals(integer, 123);\n"
+ "string = 'Goodbye';\n"
+ "assertEquals(string, 'Goodbye');");
assertEquals(engine.get("string"), "Goodbye");
// Make sure we can delete
engine.getBindings(ScriptContext.ENGINE_SCOPE).remove("string");
// This will throw because string is undefined
assertThrows(ScriptException.class, () -> {
engine.eval("let failing = string + '123';");
});
}
@Test
public void testEngineScope() throws IOException, ScriptException {
engine.put("string", "Hello");
engine.put("integer", 123);
engine.eval(new FileReader("testsrc/assert.js"));
engine.eval("assertEquals(string, 'Hello');"
+ "assertEquals(integer, 123);");
// Additional things added to the context but old stuff still there
engine.put("second", true);
engine.put("integer", 99);
engine.eval("assertEquals(string, 'Hello');"
+ "assertEquals(integer, 99);"
+ "assertTrue(second);");
}
@Test
public void testScopedBindings() throws IOException, ScriptException {
ScriptContext sc = new SimpleScriptContext();
// We treat engine and global scope the same -- if the user actually
// uses both, then engine scope overrides global scope.
Bindings eb = new SimpleBindings();
sc.setBindings(eb, ScriptContext.ENGINE_SCOPE);
eb.put("engine", Boolean.TRUE);
eb.put("level", 2);
Bindings gb = new SimpleBindings();
sc.setBindings(gb, ScriptContext.GLOBAL_SCOPE);
gb.put("global", Boolean.TRUE);
gb.put("level", 0);
engine.eval(new FileReader("testsrc/assert.js"), sc);
engine.eval("assertTrue(engine);"
+ "assertTrue(global);"
+ "assertEquals(level, 2);", sc);
}
@Test
public void testReservedBindings() throws ScriptException {
engine.put(ScriptEngine.ENGINE, "engine");
engine.put(ScriptEngine.ENGINE_VERSION, "123");
engine.put(ScriptEngine.LANGUAGE, "foo");
engine.put(ScriptEngine.NAME, "nothing");
// Can't actually test for those invalid property names -- but
// at least they didn't break the script.
assertEquals(engine.eval("'success'"), "success");
}
@Test
public void testCompiled() throws ScriptException, IOException {
CompiledScript asserts =
cEngine.compile(new FileReader("testsrc/assert.js"));
CompiledScript tests =
cEngine.compile("assertEquals(compiled, true);");
// Fails because asserts have not been loaded
assertThrows(ScriptException.class, tests::eval);
asserts.eval();
// Fails because value has not been set
assertThrows(ScriptException.class, tests::eval);
engine.put("compiled", Boolean.TRUE);
tests.eval();
}
@Test
public void testCompiled2() throws ScriptException, IOException {
CompiledScript asserts =
cEngine.compile(new FileReader("testsrc/assert.js"));
CompiledScript init =
cEngine.compile("value = 0;");
CompiledScript tests =
cEngine.compile("assertEquals(value, expectedValue);"
+ "value += 1;");
asserts.eval();
init.eval();
for (int i = 0; i <= 10; i++) {
engine.put("expectedValue", i);
tests.eval();
}
}
@Test
public void testCompiledThrows() throws ScriptException {
engine.put(ScriptEngine.FILENAME, "throws1.js");
CompiledScript throw1 = cEngine.compile("throw 'one';");
engine.put(ScriptEngine.FILENAME, "throws2.js");
CompiledScript throw2 = cEngine.compile("throw 'two';");
try {
throw1.eval();
fail("Expected a throw");
} catch (ScriptException se) {
assertTrue(se.getMessage().startsWith("one"));
assertEquals("throws1.js", se.getFileName());
assertEquals(1, se.getLineNumber());
}
try {
throw2.eval();
fail("Expected a throw");
} catch (ScriptException se) {
assertTrue(se.getMessage().startsWith("two"));
assertEquals("throws2.js", se.getFileName());
assertEquals(1, se.getLineNumber());
}
}
@Test
public void testCantCompile() {
assertThrows(ScriptException.class, () -> {
cEngine.compile("This is not JavaScript at all!");
});
}
@Test
public void testLanguageVersion() throws ScriptException {
// Default language version is modernish
ScriptEngine newEngine = manager.getEngineByName("rhino");
assertEquals(newEngine.eval("Symbol() == Symbol()"), Boolean.FALSE);
// Older language versions
ScriptEngine oldEngine = manager.getEngineByName("rhino");
oldEngine.put(ScriptEngine.LANGUAGE_VERSION, 120);
assertThrows(ScriptException.class, () -> {
oldEngine.eval("Symbol() == Symbol()");
});
// The same with a string
ScriptEngine olderEngine = manager.getEngineByName("rhino");
olderEngine.put(ScriptEngine.LANGUAGE_VERSION, "100");
assertThrows(ScriptException.class, () -> {
olderEngine.eval("Symbol() == Symbol()");
});
}
@Test
public void testBadLanguageVersion() {
assertThrows(ScriptException.class, () -> {
engine.put(ScriptEngine.LANGUAGE_VERSION, "Not a number");
engine.eval("print('Hi!');");
});
assertThrows(ScriptException.class, () -> {
engine.put(ScriptEngine.LANGUAGE_VERSION, 3.14);
engine.eval("print('Hi!');");
});
}
@Test
public void testFilename() {
engine.put(ScriptEngine.FILENAME, "test.js");
try {
engine.eval("throw 'This is an exception';");
} catch (ScriptException se) {
assertEquals(se.getFileName(), "test.js");
}
}
@Test
public void testJavaObject() throws ScriptException {
File f = new File("testsrc/assert.js");
String absVal = f.getAbsolutePath();
engine.put("file", f);
Object result = engine.eval("file.getAbsolutePath();");
assertEquals(absVal, result);
}
}