ADDITIONAL SYSTEM INFORMATION :
Tested on 64-Bit-Ubuntu Linux, OpenJDK 11.0.5
Tested on Windows-7-64-Bit, Oracle Java 8
A DESCRIPTION OF THE PROBLEM :
Testcase that demonstrates that FXMLLoader does not set [1]
ScriptEngine.FILENAME and [2] ScriptEngine.ARGV entries in
ScriptContext.ENGINE_SCOPE Bindings.
To run the test case:
- unzip testcaseFXMLLoaderScriptEngines.zip,
- change into "testcaseFXMLLoaderScriptEngines" subdirectory,
- run the testcase by issuing the following command:
- Unix:
java -cp .:RgfPseudoScriptEngine.jar FXMLLoaderTestCase4ScriptEngineScope
- Windows:
java -cp .;RgfPseudoScriptEngine.jar FXMLLoaderTestCase4ScriptEngineScope
FXMLLoaderTestCase4ScriptEngineScope loads "demo_01.fxml" which is a controller
that uses the pseudo script language rgf.scriptEngine.RgfPseudoScriptEngine,
which logs the eval() invocations with the script code and the Bindings of the
ScriptContext.
Comparing "demo_01.fxml" and the output of the above test case demonstrates that
FXMLLoader does not popuplate the [3] ENGINE_SCOPE Bindings with the filename of
the script that gets evaluated, nor does add the ARGV entry to the ENGINE_SCOPE
Bindings in the case of events (just an entry named "event").
In case of errors (during development or deployment) it is not possible
therefore to point to the file (external file or the fxml-file defining
explicitly script code) in which a runtime error has occurred.
In the case of an event callback, if ARGV was defined the script code could
directly fetch and interact with the supplied event object argument. In the
case that a script engine does not automatically make Bindings entry available
as implicit variables (e.g. for scoping reasons) it becomes cumbersome or even
impossible in some script engine implementations (if they do not provide access
to the Bindings) to get at the event argument of the callback invocation.
The JSR-223 specifications lists all the (reserved) ScriptEngine constants that
are meant to be used as reserved keys for the ENGINE_SCOPE Bindings, whenever
appropriate cf. [0] p. l50 ff. (A ScriptEngine is supposed to populate and
maintain the ENGINE_SCOPE Bindings hence the definition of these constants with
the class.)
Running the above program on Windows yields the output captured and supplied in
[4].
The supplied patch [5] changes FXMLLoader.java such,
- that the ScriptEngine.FILENAME entry is always set in the ENGINE_SCOPE
Bindings. In the case that the scripts are hosted in a fxml-file that file
path will be used together with an appended string that hints at the location
in the fxml file from where the script has been taken for evaluation. In the
case of event handling scripts that appended string hints at the location in
the fxml-file where the event attribute with the script code got used.
- and that the ScriptEngine.ARGV entry is always set in the ENGINE_SCOPE
Bindings for event callbacks using the 'event' object as the single argument.
Applying the patch and running the above program on Linux yields the output
captured and supplied in [6].
---
The jar-file [7] needs merely to be put on the CLASSPATH (or MODULE_PATH as a
proper module-info.class is included, the module name is "rgf.scriptEngine") to
make the pseudo scripting language available and to run the supplied testcase.
The RgfPseudoScriptEngine (script engine name and extension is "rpsl")
implementation should also make it possible to create test units for asserting
that Java script hosts are populating the ScriptContext Bindings according to
specifications.
All Java classes come with their source code (the script engine service file and
module-info.{java|class} are contained in the jar file).
Having signed the OCA you may use all of the supplied code freely.
If there is anything you need or that I could provide, please let me know.
---rony
[0] JSR-223 specification at <https://jcp.org/en/jsr/detail?id=223>, download
<https://jcp.org/aboutJava/communityprocess/pfd/jsr223/index.html>:
"java_scripting-1_0-fr-spec.pdf"
[1] <https://docs.oracle.com/en/java/javase/11/docs/api/java.scripting/javax/script/ScriptEngine.html#FILENAME>
[2] <https://docs.oracle.com/en/java/javase/11/docs/api/java.scripting/javax/script/ScriptEngine.html#ARGV>
[3] <https://docs.oracle.com/en/java/javase/11/docs/api/java.scripting/javax/script/ScriptContext.html#ENGINE_SCOPE>
[4] Output of running the testcase on Windows (Java 8): "info/Demo_output_without_fix.txt"
[5] FXMLLoader patch: "info/diff_add_FILENAME_ARGV_to_FXMLLoader_preferable_20191121.txt"
[6] Output of running the testcase after patching FXMLLoader with [5] on Linux (OpenJDK 11.0.5):
"info/Demo_output_with_fix_and_linenumbers.txt"
[7] Pseudo script engine implementation to be put on the CLASSPATH or MODULE_PATH (module
name "rgf.scriptEngine"): <RgfPseudoScriptEngine.jar>
[8] FXML test case:
- FXMLLoaderTestCase4ScriptEngineScope.{java|class}
... loads "demo_01.rxml" and dumps the engine and global Bindings
- demo_01.fxml
... FXML file using scripts in the pseudo script language [7] as controller,
either as external or embedded scripts, including scripts for event
handling Action and MouseClicked events
- demo_01_bottomscript.rpsl ... serving as external script file
- demo_01_middlescript.rpsl ... serving as external script file
- demo_01_topscript.rpsl ... serving as external script file
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the test case program "FXMLLoaderTestCase4ScriptEngineScope.java" which is an FXML application using an artificial script engine ("RgfPseudoScriptEngine.java", "RgfPseudoScriptEngineFactory.java") that is used as the controller language for "demo_01.fxml". "RgfPseudoScriptEngine.java" logs all eval() invocations together with the submitted script code and the ScriptContext Bindings for ENGINE_SCOPE and GLOBAL_SCOPE.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Listing eval() invocation information (evalDataList):
ScriptEngine: [rgf.scriptEngine.RgfPseudoScriptEngine@51521cc1]
eval() invocation # 1:
script: [demo_01_topscript.rpsl file - pseudo script in external file, starts with the filename
]
Bindings for scope # 100 (ENGINE_SCOPE):
Bindings for scope # 200 (GLOBAL_SCOPE):
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 2:
script: [demo_01_middlescript.rpsl file - pseudo script in external file, starts with the filename
]
Bindings for scope # 100 (ENGINE_SCOPE):
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 3:
script: [demo_01.fxml embedded script rpsl - line # 25]
Bindings for scope # 100 (ENGINE_SCOPE):
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 4:
script: [demo_01_bottomscript.rpsl file - pseudo script in external file, starts with the filename
]
Bindings for scope # 100 (ENGINE_SCOPE):
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 5:
script: [something (line # 29)
in (line # 30)
the (line # 31)
news (line # 32) ]
Bindings for scope # 100 (ENGINE_SCOPE):
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 6:
script: [demo_01.fxml (line # 32):
CDATA-section ("<![CDATA[") allows any characters including <, > and & !! (no need to escape
these special characters; it is plain CDATA which does not get processed, just passed on
including LF etc.
Watch out that in the code there is no string that exactly matches the end tag
for a CDATA-section (close-square-bracket+close-square-bracket+greater-character ]
Bindings for scope # 100 (ENGINE_SCOPE):
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 7:
script: [demo_01.fxml embedded event - ActionEvent - line # 18 - LF entity ( ) forces linebreak in attribute value:
(this is on a new line) these characters in attribute values need to be escaped: <, >, &, these if used as delimiters: ", ']
Bindings for scope # 100 (ENGINE_SCOPE):
[event]: [javafx.event.ActionEvent[source=Button[id=idButton, styleClass=button]'Press me!']]
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 8:
script: [demo_01.fxml embedded event - ActionEvent - line # 18 - LF entity ( ) forces linebreak in attribute value:
(this is on a new line) these characters in attribute values need to be escaped: <, >, &, these if used as delimiters: ", ']
Bindings for scope # 100 (ENGINE_SCOPE):
[event]: [javafx.event.ActionEvent[source=Button[id=idButton, styleClass=button]'Press me!']]
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 9:
script: [demo_01.fxml embedded event - MouseClicked - line # 17]
Bindings for scope # 100 (ENGINE_SCOPE):
[event]: [MouseEvent [source = Button[id=idButton, styleClass=button]'Press me!', target = Button[id=idButton, styleClass=button]'Press me!', eventType = MOUSE_CLICKED, consumed = false, x = -210.0, y = -137.0, z = 0.0, button = PRIMARY, primaryButtonDown, pickResult = PickResult [node = null, point = Point3D [x = 0.0, y = 0.0, z = 0.0], distance = 1.0]]
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
ACTUAL -
Gtk-Message: 13:11:24.484: Failed to load module "topmenu-gtk-module"
Listing eval() invocation information (evalDataList):
ScriptEngine: [rgf.scriptEngine.RgfPseudoScriptEngine@4fb64261]
eval() invocation # 1:
script: [demo_01_topscript.rpsl file - pseudo script in external file, starts with the filename
]
Bindings for scope # 100 (ENGINE_SCOPE):
[javax.script.filename]: [demo_01_topscript.rpsl]
Bindings for scope # 200 (GLOBAL_SCOPE):
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 2:
script: [demo_01_middlescript.rpsl file - pseudo script in external file, starts with the filename
]
Bindings for scope # 100 (ENGINE_SCOPE):
[javax.script.filename]: [demo_01_middlescript.rpsl]
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 3:
script: [demo_01.fxml embedded script rpsl - line # 25]
Bindings for scope # 100 (ENGINE_SCOPE):
[javax.script.filename]: [demo_01.fxml_at_line_25]
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 4:
script: [demo_01_bottomscript.rpsl file - pseudo script in external file, starts with the filename
]
Bindings for scope # 100 (ENGINE_SCOPE):
[javax.script.filename]: [demo_01_bottomscript.rpsl]
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 5:
script: [something (line # 29)
in (line # 30)
the (line # 31)
news (line # 32) ]
Bindings for scope # 100 (ENGINE_SCOPE):
[javax.script.filename]: [demo_01.fxml_at_line_29]
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 6:
script: [demo_01.fxml (line # 32):
CDATA-section ("<![CDATA[") allows any characters including <, > and & !! (no need to escape
these special characters; it is plain CDATA which does not get processed, just passed on
including LF etc.
Watch out that in the code there is no string that exactly matches the end tag
for a CDATA-section (close-square-bracket+close-square-bracket+greater-character ]
Bindings for scope # 100 (ENGINE_SCOPE):
[javax.script.filename]: [demo_01.fxml_at_line_32]
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 7:
script: [demo_01.fxml embedded event - ActionEvent - line # 18 - LF entity ( ) forces linebreak in attribute value:
(this is on a new line) these characters in attribute values need to be escaped: <, >, &, these if used as delimiters: ", ']
Bindings for scope # 100 (ENGINE_SCOPE):
[event]: [javafx.event.ActionEvent[source=Button[id=idButton, styleClass=button]'Press me!']]
[javax.script.argv]: [[Ljava.lang.Object;@42607a4f]
[javax.script.filename]: [demo_01.fxml_defined_by_control_before_line_19]
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 8:
script: [demo_01.fxml embedded event - ActionEvent - line # 18 - LF entity ( ) forces linebreak in attribute value:
(this is on a new line) these characters in attribute values need to be escaped: <, >, &, these if used as delimiters: ", ']
Bindings for scope # 100 (ENGINE_SCOPE):
[event]: [javafx.event.ActionEvent[source=Button[id=idButton, styleClass=button]'Press me!']]
[javax.script.argv]: [[Ljava.lang.Object;@782663d3]
[javax.script.filename]: [demo_01.fxml_defined_by_control_before_line_19]
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
eval() invocation # 9:
script: [demo_01.fxml embedded event - MouseClicked - line # 17]
Bindings for scope # 100 (ENGINE_SCOPE):
[event]: [MouseEvent [source = Button[id=idButton, styleClass=button]'Press me!', target = Button[id=idButton, styleClass=button]'Press me!', eventType = MOUSE_CLICKED, consumed = false, x = -210.0, y = -137.0, z = 0.0, button = PRIMARY, primaryButtonDown, pickResult = PickResult [node = null, point = Point3D [x = 0.0, y = 0.0, z = 0.0], distance = 1.0]]
[javax.script.argv]: [[Ljava.lang.Object;@31f924f5]
[javax.script.filename]: [demo_01.fxml_defined_by_control_before_line_19]
Bindings for scope # 200 (GLOBAL_SCOPE):
[idButton]: [Button[id=idButton, styleClass=button]'Press me!']
[idRoot]: [AnchorPane[id=idRoot, styleClass=root]]
[location]: [file:demo_01.fxml]
[resources]: [null]
---------- BEGIN SOURCE ----------
test case consists of multiple files, such that a single source code file does not yield the above output.
Packed up the source code and compiled version in a zip-archive with readme.txt for easier testing and analyzes. Please advise how to submit.
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
There is no workaround possible, unfortunately.
FREQUENCY : always