FXMLLoader: not supplying filename to script engine, not supplying event object as argument to script

Rony G. Flatscher Rony.Flatscher at wu.ac.at
Wed Nov 6 15:05:01 UTC 2019


Using a script engine (javax.script.ScriptEngine) for implementing a FXML controller there are two
important information missing in the ScriptContext.ENGINE_SCOPE Bindings supplied to the script used
to eval() the script code:

  * ScriptEngine.FILENAME
      o This value denotes the file name from where the script code was fetched that is being eval()'d.
      o When debugging script controllers in a complex JavaFX application it is mandatory to know
        the file name the script code was taken from (as such scripts could be called/run from
        different FXML files). Also, in the case of script runtime errors, usually the file name is
        given by the script engine where the error has occurred to ease debugging, such that it is
        important to really supply the filename.
          + Note: the 'location'-URL in ScriptContext.GLOBAL_SCOPE refers the FXML file,  not to the
            file that hosts the script that gets run if using the "<fx:script" element where the
            "source" attribute denotes the name of the script file.
      o General solution: supply the appropriate ScriptEngine.FILENAME entry to the
        ScriptContext.ENGINE_SCOPE Bindings.

  * ScriptEngine.ARGV
      o This value denotes the arguments that get passed to the script from Java in form of a Java
        Array of type Object.
      o When defining event handlers in FXML files in script code the script does not get the
        appropriate argument. Rather the script programmer needs to access the
        ScriptContext.ENGINE_SCOPE and fetch the entry named "event" from there. Some script engines
        may make the entries in the Bindings implicitly available to the scripts, however this
        cannot be expected by default. However, a ScriptEngine.ARGV entry must be supplied to the
        script by the script engine implementor, such that a script coder gets the event object
        argument in the script language's manner.
      o General solution: supply the appropriate ScriptEngine.ARGV Object array to the
        ScriptContext.ENGINE_SCOPE Bindings.

With these two changes not only writing controller scripts would be eased, it also would
instrumentate ScriptContext.ENGINE_SCOPE Bindings the way it was intended by JSR-223.

Enclosed please find a tested diff for FXMLLoader.java from the current OpenJavaFX Master (version
14) that implements both, ScriptEngine.FILENAME entries for all script invocations and in the case
of a script event handler the appropriate ScriptEngine.ARGV entry gets supplied, allowing the script
to fetch the event object directly as an argument.

As I have signed the OCA the code (in form of a git diff) can be directly applied to FXMLLoader.java.

If you need the patch in a different form, then please advise.

---rony






-------------- next part --------------
diff --git a/modules/javafx.fxml/src/main/java/javafx/fxml/FXMLLoader.java b/modules/javafx.fxml/src/main/java/javafx/fxml/FXMLLoader.java
index 7f3d2f3083..eab4541659 100644
--- a/modules/javafx.fxml/src/main/java/javafx/fxml/FXMLLoader.java
+++ b/modules/javafx.fxml/src/main/java/javafx/fxml/FXMLLoader.java
@@ -1558,6 +1558,12 @@ public class FXMLLoader {
                         location = new URL(FXMLLoader.this.location, source);
                     }
 
+                    Bindings engineBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
+                    Bindings localBindings = engine.createBindings();
+                    localBindings.put(engine.FILENAME, location.getPath());
+                    localBindings.putAll(engineBindings);
+                    scriptEngine.setBindings(localBindings, ScriptContext.ENGINE_SCOPE);
+
                     InputStreamReader scriptReader = null;
                     try {
                         scriptReader = new InputStreamReader(location.openStream(), charset);
@@ -1569,6 +1575,9 @@ public class FXMLLoader {
                             scriptReader.close();
                         }
                     }
+
+                    // Restore the original bindings
+                    engine.setBindings(engineBindings, ScriptContext.ENGINE_SCOPE);
                 } catch (IOException exception) {
                     throw constructLoadException(exception);
                 }
@@ -1582,7 +1591,16 @@ public class FXMLLoader {
             if (value != null && !staticLoad) {
                 // Evaluate the script
                 try {
+                    Bindings engineBindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
+                    Bindings localBindings = scriptEngine.createBindings();
+                    localBindings.put(scriptEngine.FILENAME, location.getPath());
+                    localBindings.putAll(engineBindings);
+                    scriptEngine.setBindings(localBindings, ScriptContext.ENGINE_SCOPE);
+
                     scriptEngine.eval((String)value);
+
+		    // Restore the original bindings
+                    scriptEngine.setBindings(engineBindings, ScriptContext.ENGINE_SCOPE);
                 } catch (ScriptException exception) {
                     System.err.println(exception.getMessage());
                 }
@@ -1687,6 +1705,9 @@ public class FXMLLoader {
             Bindings engineBindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
             Bindings localBindings = scriptEngine.createBindings();
             localBindings.put(EVENT_KEY, event);
+            localBindings.put(scriptEngine.ARGV, new Object[]{event});
+            URL location=(URL) scriptEngine.getBindings(ScriptContext.GLOBAL_SCOPE).get(LOCATION_KEY);
+            localBindings.put(scriptEngine.FILENAME, location.getPath());
             localBindings.putAll(engineBindings);
             scriptEngine.setBindings(localBindings, ScriptContext.ENGINE_SCOPE);
 


More information about the openjfx-dev mailing list