The JavaFX Application source does some funky stuff by parsing stack traces to determine and check the caller (e.g. to ensure that it is only called from a class which extends Application).
/**
* Launch a standalone application. This method is typically called
* from the main method(). It must not be called more than once or an
* exception will be thrown.
* This is equivalent to launch(TheClass.class, args) where TheClass is the
* immediately enclosing class of the method that called launch. It must
* be a subclass of Application or a RuntimeException will be thrown.
*
* <p>
* The launch method does not return until the application has exited,
* either via a call to Platform.exit or all of the application windows
* have been closed.
*
* <p>
* Typical usage is:
* <ul>
* <pre>
* public static void main(String[] args) {
* Application.launch(args);
* }
* </pre>
* </ul>
*
* @param args the command line arguments passed to the application.
* An application may get these parameters using the
* {@link #getParameters()} method.
*
* @throws IllegalStateException if this method is called more than once.
*/
public static void launch(String... args) {
// Figure out the right class to call
StackTraceElement[] cause = Thread.currentThread().getStackTrace();
boolean foundThisMethod = false;
String callingClassName = null;
for (StackTraceElement se : cause) {
// Skip entries until we get to the entry for this class
String className = se.getClassName();
String methodName = se.getMethodName();
if (foundThisMethod) {
callingClassName = className;
break;
} else if (Application.class.getName().equals(className)
&& "launch".equals(methodName)) {
foundThisMethod = true;
}
}
if (callingClassName == null) {
throw new RuntimeException("Error: unable to determine Application class");
}
try {
Class theClass = Class.forName(callingClassName, true,
Thread.currentThread().getContextClassLoader());
if (Application.class.isAssignableFrom(theClass)) {
Class<? extends Application> appClass = theClass;
LauncherImpl.launchApplication(appClass, args);
} else {
throw new RuntimeException("Error: " + theClass
+ " is not a subclass of javafx.application.Application");
}
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
The LauncherImpl code makes use of a getAndSet operation on a private static AtomicBoolean to ensure that the application is not launched more than once.
// Ensure that launchApplication method is only called once
private static AtomicBoolean launchCalled = new AtomicBoolean(false);
/**
* This method is called by the standalone launcher.
* It must not be called more than once or an exception will be thrown.
*
* Note that it is always called on a thread other than the FX application
* thread, since that thread is only created at startup.
*
* @param appClass application class
* @param preloaderClass preloader class, may be null
* @param args command line arguments
*/
public static void launchApplication(final Class<? extends Application> appClass,
final Class<? extends Preloader> preloaderClass,
final String[] args) {
if (launchCalled.getAndSet(true)) {
throw new IllegalStateException("Application launch must not be called more than once");
}
if (! Application.class.isAssignableFrom(appClass)) {
throw new IllegalArgumentException("Error: " + appClass.getName()
+ " is not a subclass of javafx.application.Application");
}
if (preloaderClass != null && ! Preloader.class.isAssignableFrom(preloaderClass)) {
throw new IllegalArgumentException("Error: " + preloaderClass.getName()
+ " is not a subclass of javafx.application.Preloader");
}
// Create a new Launcher thread and then wait for that thread to finish
final CountDownLatch launchLatch = new CountDownLatch(1);
Thread launcherThread = new Thread(new Runnable() {
@Override public void run() {
try {
launchApplication1(appClass, preloaderClass, args);
} catch (RuntimeException rte) {
launchException = rte;
} catch (Exception ex) {
launchException =
new RuntimeException("Application launch exception", ex);
} catch (Error err) {
launchException =
new RuntimeException("Application launch error", err);
} finally {
launchLatch.countDown();
}
}
});
launcherThread.setName("JavaFX-Launcher");
launcherThread.start();
// Wait for FX launcher thread to finish before returning to user
try {
launchLatch.await();
} catch (InterruptedException ex) {
throw new RuntimeException("Unexpected exception: ", ex);
}
if (launchException != null) {
throw launchException;
}
}
So it's kind of complicated and weird, but if you want a proven solution which works for the JavaFX code base, you could try to parse through it to understand what is going on and adapt it to your situation.
I'd say only introduce this additional complexity to your application if it is something vital for your application to have.
Orodous makes some excellent points of how obstructive such logic can be for unit testing. For example take a look at this advice on unit testing parts of a JavaFX application. Due to the weird checks in the launcher, to independently test functionality of their application, the developer needs to go through strange hoops to bypass the invoking any of the launcher code (e.g. initializing JavaFX using a Swing based JFXPanel instead of an a Application because an Application can only be launched once).