Merge pull request #787 from strangepleasures/JENA-1950

JENA-1950 Replace Nashorn with GraalVM-based JavaScript engine
diff --git a/jena-arq/pom.xml b/jena-arq/pom.xml
index 09f8322..61e7dc1 100644
--- a/jena-arq/pom.xml
+++ b/jena-arq/pom.xml
@@ -112,6 +112,20 @@
     </dependency>
 
     <dependency>
+      <groupId>org.graalvm.js</groupId>
+      <artifactId>js</artifactId>
+      <scope>compile</scope>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>org.graalvm.js</groupId>
+      <artifactId>js-scriptengine</artifactId>
+      <scope>compile</scope>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <scope>test</scope>
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/FunctionJavaScript.java b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/FunctionJavaScript.java
index 627cdff..63c0b96 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/FunctionJavaScript.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/FunctionJavaScript.java
@@ -55,8 +55,6 @@
     private final EnvJavaScript envJS;
     private final String functionName;
 
-    private boolean initialized = false;
-
     public FunctionJavaScript(String functionName, EnvJavaScript env) {
         this.functionName = functionName;
         this.envJS = env;
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/JSEngine.java b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/JSEngine.java
index 9084beb..0eff562 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/JSEngine.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/JSEngine.java
@@ -36,13 +36,7 @@
 
 /** Abstraction of a <em>per-thread</em> JavaScript execution system */  
 public class JSEngine {
-    private ScriptEngine scriptEngine;
-    private CompiledScript compiledScript;
-    
     private final Invocable invoc;
-    private String functions;
-
-    private String functionLibFile;
 
 
     /** Create a {@code JSEngine} from a string. */ 
@@ -55,24 +49,29 @@
         return new JSEngine(null, functionLibFile);
     }
     
-    /*package*/ JSEngine(String functions, String functionLibFile) { 
-        this.functions = functions;
-        this.functionLibFile = functionLibFile;
+    /*package*/ JSEngine(String functions, String functionLibFile) {
         invoc = build(functions, functionLibFile);
     }
 
     private static Invocable build(String functions, String functionLibFile) {
         if ( functions == null && functionLibFile == null )
-            throw new ARQException("Both script string and script filename are null"); 
+            throw new ARQException("Both script string and script filename are null");
 
         ScriptEngineManager manager = new ScriptEngineManager();
-        ScriptEngine scriptEngine = manager.getEngineByName("nashorn");
-        
+        ScriptEngine scriptEngine = manager.getEngineByName("javascript");
+        if (scriptEngine == null) {
+            throw new ARQException("Could not load JavaScript script engine. " +
+                    "Make sure that org.graalvm.js:js and org.graalvm.js:js-scriptengine are added to the class path");
+        }
+
+        if (scriptEngine.getFactory().getEngineName().equals("Graal.js")) {
+            scriptEngine.getContext().setAttribute("polyglot.js.nashorn-compat", true, ScriptContext.ENGINE_SCOPE);
+        }
+
         Invocable invoc = (Invocable)scriptEngine;
         if ( functionLibFile != null ) {
-            try {
-                Reader reader = Files.newBufferedReader(Paths.get(functionLibFile), StandardCharsets.UTF_8);
-                Object x = scriptEngine.eval(reader);
+            try (Reader reader = Files.newBufferedReader(Paths.get(functionLibFile), StandardCharsets.UTF_8)) {
+                scriptEngine.eval(reader);
             }
             catch (NoSuchFileException | FileNotFoundException ex) {
                 throw new RiotNotFoundException("File: "+functionLibFile);
@@ -84,7 +83,7 @@
         }
         if ( functions != null ) {
             try {
-                Object x = scriptEngine.eval(functions);
+                scriptEngine.eval(functions);
             }
             catch (ScriptException e) {
                 throw new ExprBuildException("Failed to load Javascript", e);
@@ -94,7 +93,7 @@
         // Try to call the init function - ignore NoSuchMethodException 
         try {
             invoc.invokeFunction(ARQConstants.JavaScriptInitFunction);
-        } catch (NoSuchMethodException ex) {}
+        } catch (NoSuchMethodException ignore) {}
         catch (ScriptException ex) {
             throw new ARQException("Failed to call JavaScript initialization function", ex);
         }
@@ -104,6 +103,4 @@
     public Object call(String functionName, Object[] args) throws NoSuchMethodException, ScriptException {
         return invoc.invokeFunction(functionName, args);
     }
-
-    
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/NV.java b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/NV.java
index 50e982c..c73480d 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/NV.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/NV.java
@@ -31,7 +31,7 @@
 /**
  * General representation of an {@link NodeValue} for JavaScript. Conversion is to native
  * types where possible, otherwise {@code NV}. {@code NV.toString} of a URI returns the
- * uri as a string so {@code NV} works naturally in Java/Nashorn.
+ * uri as a string so {@code NV} works naturally in Java/JavaScript.
  *
  * @see #fromNodeValue
  * @see #toNodeValue
@@ -54,7 +54,7 @@
      */
     private final static boolean narrowDoubles = true;
     /**
-     * Map an ARQ {@link NodeValue} to java/Nashorn representation of a JavaScript object.
+     * Map an ARQ {@link NodeValue} to java/Nashorn/GraalVM representation of a JavaScript object.
      * Native JavaScript types supported are null, string, number and boolean.
      * Otherwise a {@link NV} is returned.
      */
@@ -76,7 +76,7 @@
         return new NV(nv);
     }
     /**
-     * Map a java/Nashorn representation of a JavaScript object to an ARQ
+     * Map a java/Nashorn/GraalVM representation of a JavaScript object to an ARQ
      * {@link NodeValue}. Identified types are null, string, number and boolean and also
      * {@code NV} returned by the JavaScript code.
      */
@@ -103,7 +103,7 @@
         throw new ExprEvalException("Can't convert '"+r+"' to a NodeValue.  r is of class "+r.getClass().getName());
     }
 
-    // Convert the numeric values that Nashorn can return.
+    // Convert the numeric values that JavaScript can return.
     static NodeValue number2value(Number r) {
         if ( r instanceof Integer )
             return NodeValue.makeInteger((Integer)r);
diff --git a/pom.xml b/pom.xml
index e862fad..2f28792 100644
--- a/pom.xml
+++ b/pom.xml
@@ -102,6 +102,8 @@
     <ver.awaitility>3.1.0</ver.awaitility>
     <ver.micrometer>1.2.1</ver.micrometer>
 
+    <ver.graalvm>20.2.0</ver.graalvm>
+
     <jdk.version>1.8</jdk.version>
     <targetJdk>${jdk.version}</targetJdk>
 
@@ -559,6 +561,18 @@
       </dependency>
 
       <dependency>
+        <groupId>org.graalvm.js</groupId>
+        <artifactId>js</artifactId>
+        <version>${ver.graalvm}</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.graalvm.js</groupId>
+        <artifactId>js-scriptengine</artifactId>
+        <version>${ver.graalvm}</version>
+      </dependency>
+
+      <dependency>
         <groupId>org.xenei</groupId>
         <artifactId>junit-contracts</artifactId>
         <version>${ver.contract.tests}</version>
@@ -602,6 +616,7 @@
         <artifactId>micrometer-registry-prometheus</artifactId>
           <version>${ver.micrometer}</version>
       </dependency>
+
     </dependencies>
 
   </dependencyManagement>