Just for the record: attached the wrong diff, so attaching the correct one to 
this posting. Sorry
for the confusion.

---rony


On 13.11.2019 15:14, Rony G. Flatscher wrote:
> Hmm, not getting any feedback so far, so wondering if there are currently any 
> Java developers who
> take advantage of the ability of FXMLLoader to have FXML controllers 
> implemented in any of the Java
> javax.script languages?
>
> For those, who use scripting languages for FXML controllers the request that 
> FXMLLoader adds both
> entries, ScriptEngine.FILENAME (for debugging, logging) and ScriptEngine.ARGV 
> () (for making the
> event object available directly as an argument) into the engine Bindings, 
> should be quite helpful
> while developing and running the scripts.
>
> [Personally I am using the scripting engine ooRexx successfully for teaching 
> oo programming from
> scratch to JavaFX in a single semester (four hour lecture, eight ECTS) at a 
> Business Administration
> university. So these two missing features in the current FXMLLoader support 
> for FXML controllers
> would help tremendously, especially in case of coding errors as currently it 
> is not clear from which
> file the script that has an error comes from, making it extremely cumbersome 
> and time consuming in
> JavaFX applications that use multiple and complex FXML files.]
>
> Therefore I would kindly ask interested committers for mentoring the proposed 
> changes. Enclosed
> please find a simpler version of the patch that adds these missing features 
> to the ENGINE_SCOPE
> Bindings in the three locations where ScriptEngine.eval() gets invoked (it ).
>
> To comment this simple patch, maybe I should add a few remarks such that the 
> context becomes clear:
>
>   * invoking a script via ScriptEngine.eval() will always be accompanied with 
> a ScriptContext that
>     usually maintains two Bindings (Maps):
>
>       o one, GLOBAL_SCOPE Bindings, for global entries (used e.g. for putting 
> the FXML elements that
>         have an fx:id attribute defined, such that scripts can get access to 
> them, independent of a
>         particular ScriptEngine) which can also be used for sharing values 
> among different script
>         invocations,
>
>       o one, ENGINE_SCOPE Bindings, usually used for individual invocations.
>
>   * while a FXML file gets processed sequentially by the FXMLLoader elements 
> in the form of
>     "<fx:script source="someScript.ext" />" will cause invoking the 
> ScriptEngine.eval(Reader): this
>     patch fetches the ENGINE_SCOPE Bindings and puts the value 
> "someScript.ext" with the key
>     ScriptEngine.FILENAME into it (cf. "@@ -1558,6 +1558,9 @@ public class 
> FXMLLoader" and "@@
>     -1582,6 +1585,8 @@ public class FXMLLoader" in the patch),
>
>   * if an event handler gets defined (e.g. with the event attribute 
> "<fx:button ...
>     onAction="someScript">") the FXMLLoader creates a ScriptEventHandler and 
> stores "someScript" and
>     the ScriptEngine for executing that script whenever the event fires.
>
>       o When an event fires, the current implementation creates a copy of the 
> current ENGINE_SCOPE
>         Bindings from the ScriptEngine's ScriptContext, adds its entries to 
> it after saving the
>         entry "event" with the ActionEvent object in it. It then changes the 
> ScriptEngine's current
>         ScriptContext such that it now uses the new copy of the Bindings as 
> its ENGINE_SCOPE
>         Bindings, runs the script using eval() and then restores the 
> ScriptContext ENGINE_SCOPE
>         Bindings.
>
>       o The supplied patch (cf. "@@ -1675,30 +1680,28 @@ public class 
> FXMLLoader") instead will
>         create a copy of the ENGINE_SCOPE Bindings only once at creation time 
> (and puts the
>         appropriate ScriptEngine.FILENAME into it using the name of the FXML 
> file that defines the
>         event script attribute) and will reuse that Bindings each time the 
> handler gets invoked,
>         after putting the actual "event" object and the respective 
> ScriptEngine.ARGV entry into it.
>         Using ScriptEngine.eval(String,Bindings) will use the supplied 
> Bindings as the ENGINE_SCOPE
>         Bindings for this invocation only, such that no restore is necessary 
> upon return.
>
> As only entries get added to the engine Bindings that have not been used by 
> FXMLLoader this simple
> patch should not affect existing scripts. The patch has been tested and works.
>
> Maybe it helps the cause for applying this patch, if I point out that I have 
> been active in a number
> of opensource projects, including Apache's BSF which led to my participation 
> as an expert in JSR-223
> which originally defined the javax.script framework introduced with Java 6 
> (also authored a complete
> ScriptEngine implementation with both, the javax.script.Compilable and the 
> javax.script.Invocable
> interfaces).
>
> So looking for interested committers who would be willing to mentor this 
> patch. Please advise.
>
> ---rony
>
>
>
> On 06.11.2019 16:05, Rony G. Flatscher wrote:
>> 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

diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
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..1d56ca0614 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,9 @@ public class FXMLLoader {
                         location = new URL(FXMLLoader.this.location, source);
                     }
 
+                    Bindings engineBindings = 
engine.getBindings(ScriptContext.ENGINE_SCOPE);
+                    engineBindings.put(engine.FILENAME, location.getPath());
+
                     InputStreamReader scriptReader = null;
                     try {
                         scriptReader = new 
InputStreamReader(location.openStream(), charset);
@@ -1582,6 +1585,8 @@ public class FXMLLoader {
             if (value != null && !staticLoad) {
                 // Evaluate the script
                 try {
+                    Bindings engineBindings = 
scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
+                    engineBindings.put(scriptEngine.FILENAME, 
location.getPath());
                     scriptEngine.eval((String)value);
                 } catch (ScriptException exception) {
                     System.err.println(exception.getMessage());
@@ -1675,30 +1680,29 @@ public class FXMLLoader {
     private static class ScriptEventHandler implements EventHandler<Event> {
         public final String script;
         public final ScriptEngine scriptEngine;
+        public final Bindings engineBindings;
 
         public ScriptEventHandler(String script, ScriptEngine scriptEngine) {
             this.script = script;
             this.scriptEngine = scriptEngine;
+            engineBindings = scriptEngine.createBindings();
+            
engineBindings.putAll(scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE));
+            URL location=(URL) 
scriptEngine.getBindings(ScriptContext.GLOBAL_SCOPE).get(LOCATION_KEY);
+            engineBindings.put(scriptEngine.FILENAME, location.getPath());
         }
 
         @Override
         public void handle(Event event) {
-            // Don't pollute the page namespace with values defined in the 
script
-            Bindings engineBindings = 
scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
-            Bindings localBindings = scriptEngine.createBindings();
-            localBindings.put(EVENT_KEY, event);
-            localBindings.putAll(engineBindings);
-            scriptEngine.setBindings(localBindings, 
ScriptContext.ENGINE_SCOPE);
+            // Don't pollute the page namespace with values defined in the 
script use initial engineBindings
+            engineBindings.put(EVENT_KEY, event);
+            engineBindings.put(scriptEngine.ARGV, new Object[]{event});
 
             // Execute the script
             try {
-                scriptEngine.eval(script);
+                scriptEngine.eval(script,engineBindings);
             } catch (ScriptException exception){
                 throw new RuntimeException(exception);
             }
-
-            // Restore the original bindings
-            scriptEngine.setBindings(engineBindings, 
ScriptContext.ENGINE_SCOPE);
         }
     }
 

Reply via email to