package com.oracle.truffle.js.scriptengine;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import org.graalvm.polyglot.Engine;
public final class GraalJSEngineFactory implements ScriptEngineFactory {
private static final String ENGINE_NAME = "Graal.js";
private static final String NAME = "javascript";
private static final String LANGUAGE = "ECMAScript";
private static final String LANGUAGE_VERSION = "ECMAScript 262 Edition 11";
private static final String NASHORN_ENGINE_NAME = "Oracle Nashorn";
private static final List<String> names;
private static final List<String> mimeTypes;
private static final List<String> extensions;
public static final boolean RegisterAsNashornScriptEngineFactory = Boolean.getBoolean("graaljs.RegisterGraalJSAsNashorn");
static {
List<String> nameList = new ArrayList<>(Arrays.asList("Graal.js", "graal.js", "Graal-js", "graal-js", "Graal.JS", "Graal-JS", "GraalJS", "GraalJSPolyglot", "js", "JS", "JavaScript",
"javascript", "ECMAScript", "ecmascript"));
List<String> mimeTypeList = new ArrayList<>(Arrays.asList("application/javascript", "application/ecmascript", "text/javascript", "text/ecmascript"));
List<String> extensionList = new ArrayList<>(Arrays.asList("js", "mjs"));
boolean java8 = System.getProperty("java.specification.version").compareTo("1.9") < 0;
if (java8) {
ScriptEngineFactory nashornFactory = getNashornEngineFactory();
if (nashornFactory != null) {
if (RegisterAsNashornScriptEngineFactory) {
nameList.addAll(nashornFactory.getNames());
mimeTypeList.addAll(nashornFactory.getMimeTypes());
extensionList.addAll(nashornFactory.getExtensions());
}
clearEngineFactory(nashornFactory);
}
}
names = Collections.unmodifiableList(nameList);
mimeTypes = Collections.unmodifiableList(mimeTypeList);
extensions = Collections.unmodifiableList(extensionList);
}
private WeakReference<Engine> defaultEngine;
private final Engine userDefinedEngine;
public GraalJSEngineFactory() {
this.defaultEngine = null;
this.userDefinedEngine = null;
}
GraalJSEngineFactory(Engine engine) {
this.userDefinedEngine = engine;
}
private static Engine createDefaultEngine() {
return Engine.newBuilder().allowExperimentalOptions(true).build();
}
public Engine getPolyglotEngine() {
if (userDefinedEngine != null) {
return userDefinedEngine;
} else {
Engine engine = defaultEngine == null ? null : defaultEngine.get();
if (engine == null) {
engine = createDefaultEngine();
defaultEngine = new WeakReference<>(engine);
}
return engine;
}
}
@Override
public String getEngineName() {
return ENGINE_NAME;
}
@Override
public String getEngineVersion() {
return getPolyglotEngine().getVersion();
}
@Override
public List<String> getExtensions() {
return extensions;
}
@Override
public List<String> getMimeTypes() {
return mimeTypes;
}
@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.NAME:
return NAME;
case ScriptEngine.ENGINE:
return getEngineName();
case ScriptEngine.ENGINE_VERSION:
return getEngineVersion();
case ScriptEngine.LANGUAGE:
return getLanguageName();
case ScriptEngine.LANGUAGE_VERSION:
return getLanguageVersion();
default:
return null;
}
}
@Override
public GraalJSScriptEngine getScriptEngine() {
return new GraalJSScriptEngine(this);
}
@Override
public String getMethodCallSyntax(final String obj, final String method, final String... args) {
Objects.requireNonNull(obj);
Objects.requireNonNull(method);
final StringBuilder sb = new StringBuilder().append(obj).append('.').append(method).append('(');
final int len = args.length;
if (len > 0) {
Objects.requireNonNull(args[0]);
sb.append(args[0]);
}
for (int i = 1; i < len; i++) {
Objects.requireNonNull(args[i]);
sb.append(',').append(args[i]);
}
sb.append(')');
return sb.toString();
}
@Override
public String getOutputStatement(final String toDisplay) {
return "print(" + toDisplay + ")";
}
@Override
public String getProgram(final String... statements) {
final StringBuilder sb = new StringBuilder();
for (final String statement : statements) {
Objects.requireNonNull(statement);
sb.append(statement).append(';');
}
return sb.toString();
}
private static ScriptEngineFactory getNashornEngineFactory() {
for (ScriptEngineFactory factory : new ScriptEngineManager().getEngineFactories()) {
if (NASHORN_ENGINE_NAME.equals(factory.getEngineName())) {
return factory;
}
}
return null;
}
private static void clearEngineFactory(ScriptEngineFactory factory) {
assert factory != null;
try {
Class<?> clazz = factory.getClass();
for (String immutableListFieldName : new String[]{"names", "mimeTypes", "extensions"}) {
Field immutableListField = clazz.getDeclaredField(immutableListFieldName);
immutableListField.setAccessible(true);
Object immutableList = immutableListField.get(null);
Class<?> unmodifiableListClazz = Class.forName("java.util.Collections$UnmodifiableList");
Field unmodifiableListField = unmodifiableListClazz.getDeclaredField("list");
unmodifiableListField.setAccessible(true);
Class<?> unmodifiableCollectionClazz = Class.forName("java.util.Collections$UnmodifiableCollection");
Field unmodifiableCField = unmodifiableCollectionClazz.getDeclaredField("c");
unmodifiableCField.setAccessible(true);
List<?> list = (List<?>) unmodifiableListField.get(immutableList);
List<Object> filteredList = new ArrayList<>();
for (Object item : list) {
if (!RegisterAsNashornScriptEngineFactory && item.toString().toLowerCase().equals("nashorn")) {
filteredList.add(item);
}
}
unmodifiableListField.set(immutableList, filteredList);
unmodifiableCField.set(immutableList, filteredList);
}
} catch (NullPointerException | ClassNotFoundException | IllegalAccessException | IllegalArgumentException | NoSuchFieldException | SecurityException e) {
System.err.println("Failed to clear engine names [" + factory.getEngineName() + "]");
e.printStackTrace();
}
}
}