Here is a workaround/solution which should be relatively easy to use for anyone packaging their JavaFX application in a jar using some sort of build-tool (e.g. Maven, Gradle, Leiningen) which supports Maven dependency style coordinates:
- One small class dropped into your project
- Two small dependencies
- Two values in your manifest
- One additional arg to your java startup command
- One environment variable in your startup-script
See/get the source here: no.andante.george.Agent
(Detailed usage/help embedded in the source.)
Hope it is useful.
(Do let me know if you were able to use it successfully in a comment bellow.)
Understanding the problem and this solution
The problem is a double whammy!
Digging into the OpenJDK source you will find in 'sun.awt.X11.XToolkit' (which extends 'sun.awt.UnixToolkit' which extends 'sun.awt.SunToolkit' which extends 'java.awt.Toolkit'):
private static String awtAppClassName = null;
If you set this variable to whatever you want (with introspection) early enough (e.g. at the beginning of your main
method), your value will get picked up and passed on to the underlying GTK system when the Toolkit windowing system is initialized. This is what e.g. IntelliJ does in com.intellij.ui.AppUIUtil
However, digging into the OpenJFX source you will find in 'com.sun.class.ui.Application':
private final static String DEFAULT_NAME = "java";
protected String name = DEFAULT_NAME;
Looking at this, your first idea might be to do
com.sun.javafx.application.PlatformImpl.setApplicationName(..)
.
But that won't work because it is too late! The name
variable was set at class instantiation and then passed on to the underlying GTK system before your code got called.
So then you try to use introspection to set the constant DEFAULT_NAME
to what you want, and so that will get used to instantiate the variable name
. But that also doesn't work! Why?
Because of the keyword final
(which is the most significant difference between the Toolkit and the Application implementation)!
Because it is final static and is assigned the value "java"
, the value is "inlined" at compile time. I.e. it is inserted into the "constant pool", and all references to the constant DEFAULT_NAME
are replaced with a reference to the constant pool. And so, even if you set the constant at runtime, it will be ignored, and any code (in this case the constructor) will simply set the variable name
to the value which was compiled into the constant pool.
I don't think you can alter the constant pool or the body of the constructor with introspection. If you can, I would like to know how!
Enter ASM
But if you can rewrite the actual byte-code of the class before it gets loaded by the JVM, then you can do pretty much anything!
So I chose to write a small "Java Agent" (think of it as JVM middleware). All it does is look for the environment variable 'APPLICATION_NAME', and if found it will look for the class 'com.sun.glass.ui.Application' and transform it slightly before it is passed on to the JVM and the class loader:
First, it sets the constant DEFAULT_NAME
to the value of 'APPLICATION_NAME'. Then, it alters the body of the Application constructor such that the variable name
is assigned the value of DEFAULT_NAME
(instead of the value in the constant pool). Finally, it disables the method setName
(by removing its body).
Hope this is interesting and possibly useful to someone. Please do see (and use) my solution linked at the top of this answer. Feedback is welcomed.