1 /* 2 * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.javafx.application; 27 28 import javafx.application.Application; 29 import javafx.application.Preloader; 30 import javafx.application.Preloader.ErrorNotification; 31 import javafx.application.Preloader.PreloaderNotification; 32 import javafx.application.Preloader.StateChangeNotification; 33 import javafx.stage.Stage; 34 import java.io.File; 35 import java.io.IOException; 36 import java.lang.reflect.Constructor; 37 import java.lang.reflect.InvocationTargetException; 38 import java.lang.reflect.Method; 39 import java.net.MalformedURLException; 40 import java.net.URL; 41 import java.net.URLClassLoader; 42 import java.security.AccessController; 43 import java.security.PrivilegedAction; 44 import java.text.Normalizer; 45 import java.util.ArrayList; 46 import java.util.LinkedList; 47 import java.util.List; 48 import java.util.concurrent.CountDownLatch; 49 import java.util.concurrent.atomic.AtomicBoolean; 50 import java.util.concurrent.atomic.AtomicReference; 51 import java.util.jar.Attributes; 52 import java.util.jar.JarFile; 53 import java.util.jar.Manifest; 54 import java.util.Base64; 55 import java.util.Optional; 56 import com.sun.javafx.stage.StageHelper; 57 58 59 public class LauncherImpl { 60 /** 61 * When passed as launchMode to launchApplication, tells the method that 62 * launchName is the name of the JavaFX application class to launch. 63 */ 64 public static final String LAUNCH_MODE_CLASS = "LM_CLASS"; 65 66 /** 67 * When passed as launchMode to launchApplication, tells the method that 68 * launchName is a path to a JavaFX application jar file to be launched. 69 */ 70 public static final String LAUNCH_MODE_JAR = "LM_JAR"; 71 72 /** 73 * When passed as launchMode to launchApplication, tells the method that 74 * launchName is the name of the JavaFX application class within a module 75 * to be launched. Either the class name will be provided or the main class 76 * will be defined in the module's descriptor. 77 */ 78 public static final String LAUNCH_MODE_MODULE = "LM_MODULE"; 79 80 // set to true to debug launch issues from Java launcher 81 private static final boolean trace = false; 82 83 // set system property javafx.verbose to true to make the launcher noisy 84 private static final boolean verbose; 85 86 private static final String MF_MAIN_CLASS = "Main-Class"; 87 private static final String MF_JAVAFX_MAIN = "JavaFX-Application-Class"; 88 private static final String MF_JAVAFX_PRELOADER = "JavaFX-Preloader-Class"; 89 private static final String MF_JAVAFX_CLASS_PATH = "JavaFX-Class-Path"; 90 private static final String MF_JAVAFX_ARGUMENT_PREFIX = "JavaFX-Argument-"; 91 private static final String MF_JAVAFX_PARAMETER_NAME_PREFIX = "JavaFX-Parameter-Name-"; 92 private static final String MF_JAVAFX_PARAMETER_VALUE_PREFIX = "JavaFX-Parameter-Value-"; 93 94 // Set to true to simulate a slow download progress 95 private static final boolean simulateSlowProgress = false; 96 97 // Ensure that launchApplication method is only called once 98 private static AtomicBoolean launchCalled = new AtomicBoolean(false); 99 100 // Flag indicating that the toolkit has been started 101 private static final AtomicBoolean toolkitStarted = new AtomicBoolean(false); 102 103 // Exception found during launching 104 private static volatile RuntimeException launchException = null; 105 106 // The current preloader, used for notification in the standalone 107 // launcher mode 108 private static Preloader currentPreloader = null; 109 110 // Saved preloader class from the launchApplicationWithArgs method (called 111 // from the Java 8 launcher). It is used in the case where we call main, 112 // which is turn calls into launchApplication. 113 private static Class<? extends Preloader> savedPreloaderClass = null; 114 115 // The following is used to determine whether the main() method 116 // has set the CCL in the case where main is called after the FX toolkit 117 // is started. 118 private static ClassLoader savedMainCcl = null; 119 120 static { 121 verbose = AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> 122 Boolean.getBoolean("javafx.verbose")); 123 } 124 125 /** 126 * This method is called by the Application.launch method. 127 * It must not be called more than once or an exception will be thrown. 128 * 129 * Note that it is always called on a thread other than the FX application 130 * thread, since that thread is only created at startup. 131 * 132 * @param appClass application class 133 * @param args command line arguments 134 */ 135 @SuppressWarnings("unchecked") 136 public static void launchApplication(final Class<? extends Application> appClass, 137 final String[] args) { 138 139 Class<? extends Preloader> preloaderClass = savedPreloaderClass; 140 141 if (preloaderClass == null) { 142 String preloaderByProperty = AccessController.doPrivileged((PrivilegedAction<String>) () -> 143 System.getProperty("javafx.preloader")); 144 if (preloaderByProperty != null) { 145 try { 146 preloaderClass = (Class<? extends Preloader>) Class.forName(preloaderByProperty, 147 false, appClass.getClassLoader()); 148 } catch (Exception e) { 149 System.err.printf("Could not load preloader class '" + preloaderByProperty + 150 "', continuing without preloader."); 151 e.printStackTrace(); 152 } 153 } 154 } 155 156 launchApplication(appClass, preloaderClass, args); 157 } 158 159 /** 160 * This method is called by the standalone launcher. 161 * It must not be called more than once or an exception will be thrown. 162 * 163 * Note that it is always called on a thread other than the FX application 164 * thread, since that thread is only created at startup. 165 * 166 * @param appClass application class 167 * @param preloaderClass preloader class, may be null 168 * @param args command line arguments 169 */ 170 public static void launchApplication(final Class<? extends Application> appClass, 171 final Class<? extends Preloader> preloaderClass, 172 final String[] args) { 173 174 if (launchCalled.getAndSet(true)) { 175 throw new IllegalStateException("Application launch must not be called more than once"); 176 } 177 178 if (! Application.class.isAssignableFrom(appClass)) { 179 throw new IllegalArgumentException("Error: " + appClass.getName() 180 + " is not a subclass of javafx.application.Application"); 181 } 182 183 if (preloaderClass != null && ! Preloader.class.isAssignableFrom(preloaderClass)) { 184 throw new IllegalArgumentException("Error: " + preloaderClass.getName() 185 + " is not a subclass of javafx.application.Preloader"); 186 } 187 188 // System.err.println("launch standalone app: preloader class = " 189 // + preloaderClass); 190 191 // Create a new Launcher thread and then wait for that thread to finish 192 final CountDownLatch launchLatch = new CountDownLatch(1); 193 Thread launcherThread = new Thread(() -> { 194 try { 195 launchApplication1(appClass, preloaderClass, args); 196 } catch (RuntimeException rte) { 197 launchException = rte; 198 } catch (Exception ex) { 199 launchException = 200 new RuntimeException("Application launch exception", ex); 201 } catch (Error err) { 202 launchException = 203 new RuntimeException("Application launch error", err); 204 } finally { 205 launchLatch.countDown(); 206 } 207 }); 208 launcherThread.setName("JavaFX-Launcher"); 209 launcherThread.start(); 210 211 // Wait for FX launcher thread to finish before returning to user 212 try { 213 launchLatch.await(); 214 } catch (InterruptedException ex) { 215 throw new RuntimeException("Unexpected exception: ", ex); 216 } 217 218 if (launchException != null) { 219 throw launchException; 220 } 221 } 222 223 /** 224 * This method is called by the Java launcher. This allows us to be launched 225 * directly from the command line via "java -jar fxapp.jar", 226 * "java -cp path some.fx.App", or "java -m module/some.fx.App". The launchMode 227 * argument must be one of "LM_CLASS", "LM_JAR", or "LM_MODULE" or execution will 228 * abort with an error. 229 * 230 * @param launchName The path to a jar file, the application class name to launch, 231 * or the module and optional class name to launch 232 * @param launchMode The method of launching the application, one of LM_JAR, 233 * LM_CLASS, or LM_MODULE 234 * @param args Application arguments from the command line 235 */ 236 public static void launchApplication(final String launchName, 237 final String launchMode, 238 final String[] args) { 239 240 if (verbose) { 241 System.err.println("JavaFX launchApplication method: launchMode=" 242 + launchMode); 243 } 244 245 /* 246 * For now, just open the jar and get JavaFX-Application-Class and 247 * JavaFX-Preloader and pass them to launchApplication. In the future 248 * we'll need to load requested jar files 249 */ 250 String mainClassName = null; 251 String preloaderClassName = null; 252 String[] appArgs = args; 253 ClassLoader appLoader = null; 254 ModuleAccess mainModule = null; 255 256 if (launchMode.equals(LAUNCH_MODE_JAR)) { 257 Attributes jarAttrs = getJarAttributes(launchName); 258 if (jarAttrs == null) { 259 abort(null, "Can't get manifest attributes from jar"); 260 } 261 262 // If we ever need to check JavaFX-Version, do that here... 263 264 // Support JavaFX-Class-Path, but warn that it's deprecated if used 265 String fxClassPath = jarAttrs.getValue(MF_JAVAFX_CLASS_PATH); 266 if (fxClassPath != null) { 267 if (fxClassPath.trim().length() == 0) { 268 fxClassPath = null; 269 } else { 270 if (verbose) { 271 System.err.println("WARNING: Application jar uses deprecated JavaFX-Class-Path attribute." 272 +" Please use Class-Path instead."); 273 } 274 275 /* 276 * create a new ClassLoader to pull in the requested jar files 277 * OK if it returns null, that just means we didn't need to load 278 * anything 279 */ 280 appLoader = setupJavaFXClassLoader(new File(launchName), fxClassPath); 281 } 282 } 283 284 // process arguments and parameters if no args have been passed by the launcher 285 if (args.length == 0) { 286 appArgs = getAppArguments(jarAttrs); 287 } 288 289 // grab JavaFX-Application-Class 290 mainClassName = jarAttrs.getValue(MF_JAVAFX_MAIN); 291 if (mainClassName == null) { 292 // fall back on Main-Class if no JAC 293 mainClassName = jarAttrs.getValue(MF_MAIN_CLASS); 294 if (mainClassName == null) { 295 // Should not happen as the launcher enforces the presence of Main-Class 296 abort(null, "JavaFX jar manifest requires a valid JavaFX-Appliation-Class or Main-Class entry"); 297 } 298 } 299 mainClassName = mainClassName.trim(); 300 301 // grab JavaFX-Preloader-Class 302 preloaderClassName = jarAttrs.getValue(MF_JAVAFX_PRELOADER); 303 if (preloaderClassName != null) { 304 preloaderClassName = preloaderClassName.trim(); 305 } 306 } else if (launchMode.equals(LAUNCH_MODE_CLASS)) { 307 mainClassName = launchName; 308 } else if (launchMode.equals(LAUNCH_MODE_MODULE)) { 309 // This is largely copied from java.base/sun.launcher.LauncherHelper 310 int i = launchName.indexOf('/'); 311 String moduleName; 312 if (i == -1) { 313 moduleName = launchName; 314 mainClassName = null; 315 } else { 316 moduleName = launchName.substring(0, i); 317 mainClassName = launchName.substring(i+1); 318 } 319 320 mainModule = ModuleAccess.load(moduleName); 321 322 // get main class from module descriptor 323 if (mainClassName == null) { 324 Optional<String> omc = mainModule.getDescriptor().mainClass(); 325 if (!omc.isPresent()) { 326 abort(null, "Module %1$s does not have a MainClass attribute, use -m <module>/<main-class>", 327 moduleName); 328 } 329 mainClassName = omc.get(); 330 } 331 } else { 332 abort(new IllegalArgumentException( 333 "The launchMode argument must be one of LM_CLASS, LM_JAR or LM_MODULE"), 334 "Invalid launch mode: %1$s", launchMode); 335 } 336 337 // fall back if no MF_JAVAFX_PRELOADER attribute, or not launching using -jar 338 if (preloaderClassName == null) { 339 preloaderClassName = System.getProperty("javafx.preloader"); 340 } 341 342 if (mainClassName == null) { 343 abort(null, "No main JavaFX class to launch"); 344 } 345 346 // check if we have to load through a custom classloader 347 if (appLoader != null) { 348 try { 349 // reload this class through the app classloader 350 Class<?> launcherClass = appLoader.loadClass(LauncherImpl.class.getName()); 351 352 // then invoke the second part of this launcher using reflection 353 Method lawa = launcherClass.getMethod("launchApplicationWithArgs", 354 new Class[] { ModuleAccess.class, String.class, String.class, (new String[0]).getClass()}); 355 356 // set the thread context class loader before we continue, or it won't load properly 357 Thread.currentThread().setContextClassLoader(appLoader); 358 lawa.invoke(null, new Object[] {null, mainClassName, preloaderClassName, appArgs}); 359 } catch (Exception e) { 360 abort(e, "Exception while launching application"); 361 } 362 } else { 363 launchApplicationWithArgs(mainModule, mainClassName, preloaderClassName, appArgs); 364 } 365 } 366 367 // wrapper for Class.forName that handles cases where diacritical marks in the name 368 // cause the class to not be loaded, also largely copied from LauncherHelper.java 369 // this method returns null if the class cannot be loaded 370 private static Class<?> loadClass(final ModuleAccess mainModule, final String className) { 371 Class<?> clz = null; 372 final ClassLoader loader = Thread.currentThread().getContextClassLoader(); 373 374 // loader is ignored for modular mode 375 // the only time we need to use a separate loader is LM_JAR with 376 // a MF_JAVAFX_CLASS_PATH attribute which is deprecated 377 378 if (mainModule != null) { 379 clz = mainModule.classForName(className); 380 } else { 381 try { 382 clz = Class.forName(className, true, loader); 383 } catch (ClassNotFoundException | NoClassDefFoundError cnfe) {} 384 } 385 386 if (clz == null && System.getProperty("os.name", "").contains("OS X") 387 && Normalizer.isNormalized(className, Normalizer.Form.NFD)) { 388 // macOS may have decomposed diacritical marks in mainClassName 389 // recompose them and try again 390 String cn = Normalizer.normalize(className, Normalizer.Form.NFC); 391 392 if (mainModule != null) { 393 clz = mainModule.classForName(cn); 394 } else { 395 try { 396 clz = Class.forName(cn, true, loader); 397 } catch (ClassNotFoundException | NoClassDefFoundError cnfe) {} 398 } 399 } 400 401 return clz; 402 } 403 404 // Must be public since we could be called from a different class loader 405 public static void launchApplicationWithArgs(final ModuleAccess mainModule, 406 final String mainClassName, 407 final String preloaderClassName, String[] args) { 408 try { 409 startToolkit(); 410 } catch (InterruptedException ex) { 411 abort(ex, "Toolkit initialization error", mainClassName); 412 } 413 414 Class<? extends Application> appClass; 415 Class<? extends Preloader> preClass = null; 416 Class<?> tempAppClass = null; 417 418 final AtomicReference<Class<?>> tmpClassRef = new AtomicReference<>(); 419 final AtomicReference<Class<? extends Preloader>> preClassRef = new AtomicReference<>(); 420 PlatformImpl.runAndWait(() -> { 421 Class<?> clz = loadClass(mainModule, mainClassName); 422 if (clz == null) { 423 if (mainModule != null) { 424 abort(null, "Missing JavaFX application class %1$s in module %2$s", 425 mainClassName, mainModule.getName()); 426 } else { 427 abort(null, "Missing JavaFX application class %1$s", mainClassName); 428 } 429 } 430 431 tmpClassRef.set(clz); 432 433 if (preloaderClassName != null) { 434 // TODO: modular preloader? 435 clz = loadClass(null, preloaderClassName); 436 if (clz == null) { 437 abort(null, "Missing JavaFX preloader class %1$s", preloaderClassName); 438 } 439 440 if (!Preloader.class.isAssignableFrom(clz)) { 441 abort(null, "JavaFX preloader class %1$s does not extend javafx.application.Preloader", clz.getName()); 442 } 443 preClassRef.set(clz.asSubclass(Preloader.class)); 444 } 445 }); 446 preClass = preClassRef.get(); 447 tempAppClass = tmpClassRef.get(); 448 449 // Save the preloader class in a static field for later use when 450 // main calls back into launchApplication. 451 savedPreloaderClass = preClass; 452 453 // If there is a public static void main(String[]) method then call it 454 // otherwise just hand off to the other launchApplication method 455 456 Exception theEx = null; 457 try { 458 Method mainMethod = tempAppClass.getMethod("main", 459 new Class[] { (new String[0]).getClass() }); 460 if (verbose) { 461 System.err.println("Calling main(String[]) method"); 462 } 463 savedMainCcl = Thread.currentThread().getContextClassLoader(); 464 mainMethod.invoke(null, new Object[] { args }); 465 return; 466 } catch (NoSuchMethodException | IllegalAccessException ex) { 467 theEx = ex; 468 savedPreloaderClass = null; 469 if (verbose) { 470 System.err.println("WARNING: Cannot access application main method: " + ex); 471 } 472 } catch (InvocationTargetException ex) { 473 ex.printStackTrace(); 474 abort(null, "Exception running application %1$s", tempAppClass.getName()); 475 return; 476 } 477 478 // Verify appClass extends Application 479 if (!Application.class.isAssignableFrom(tempAppClass)) { 480 abort(theEx, "JavaFX application class %1$s does not extend javafx.application.Application", tempAppClass.getName()); 481 } 482 appClass = tempAppClass.asSubclass(Application.class); 483 484 if (verbose) { 485 System.err.println("Launching application directly"); 486 } 487 launchApplication(appClass, preClass, args); 488 } 489 490 private static URL fileToURL(File file) throws IOException { 491 return file.getCanonicalFile().toURI().toURL(); 492 } 493 494 private static ClassLoader setupJavaFXClassLoader(File appJar, String fxClassPath) { 495 try { 496 File baseDir = appJar.getParentFile(); 497 ArrayList jcpList = new ArrayList(); 498 499 // Add in the jars from the JavaFX-Class-Path entry 500 // TODO: should check current classpath for duplicate entries and ignore them 501 String cp = fxClassPath; 502 if (cp != null) { 503 // these paths are relative to baseDir, which should be the 504 // directory containing the app jar file 505 while (cp.length() > 0) { 506 int pathSepIdx = cp.indexOf(" "); 507 if (pathSepIdx < 0) { 508 String pathElem = cp; 509 File f = (baseDir == null) ? 510 new File(pathElem) : new File(baseDir, pathElem); 511 if (f.exists()) { 512 jcpList.add(fileToURL(f)); 513 } else if (verbose) { 514 System.err.println("Class Path entry \""+pathElem 515 +"\" does not exist, ignoring"); 516 } 517 break; 518 } else if (pathSepIdx > 0) { 519 String pathElem = cp.substring(0, pathSepIdx); 520 File f = (baseDir == null) ? 521 new File(pathElem) : new File(baseDir, pathElem); 522 if (f.exists()) { 523 jcpList.add(fileToURL(f)); 524 } else if (verbose) { 525 System.err.println("Class Path entry \""+pathElem 526 +"\" does not exist, ignoring"); 527 } 528 } 529 cp = cp.substring(pathSepIdx + 1); 530 } 531 } 532 533 // don't bother if there's nothing to add 534 if (!jcpList.isEmpty()) { 535 ArrayList<URL> urlList = new ArrayList<URL>(); 536 537 // prepend the existing classpath 538 // this will already have the app jar, so no need to worry about it 539 cp = System.getProperty("java.class.path"); 540 if (cp != null) { 541 while (cp.length() > 0) { 542 int pathSepIdx = cp.indexOf(File.pathSeparatorChar); 543 if (pathSepIdx < 0) { 544 String pathElem = cp; 545 urlList.add(fileToURL(new File(pathElem))); 546 break; 547 } else if (pathSepIdx > 0) { 548 String pathElem = cp.substring(0, pathSepIdx); 549 urlList.add(fileToURL(new File(pathElem))); 550 } 551 cp = cp.substring(pathSepIdx + 1); 552 } 553 } 554 555 // and finally append the JavaFX-Class-Path entries 556 urlList.addAll(jcpList); 557 558 URL[] urls = (URL[])urlList.toArray(new URL[0]); 559 if (verbose) { 560 System.err.println("===== URL list"); 561 for (int i = 0; i < urls.length; i++) { 562 System.err.println("" + urls[i]); 563 } 564 System.err.println("====="); 565 } 566 return new URLClassLoader(urls, ClassLoader.getPlatformClassLoader()); 567 } 568 } catch (Exception ex) { 569 if (trace) { 570 System.err.println("Exception creating JavaFX class loader: "+ex); 571 ex.printStackTrace(); 572 } 573 } 574 return null; 575 } 576 577 private static String decodeBase64(String inp) throws IOException { 578 return new String(Base64.getDecoder().decode(inp)); 579 } 580 581 private static String[] getAppArguments(Attributes attrs) { 582 List args = new LinkedList(); 583 584 try { 585 int idx = 1; 586 String argNamePrefix = MF_JAVAFX_ARGUMENT_PREFIX; 587 while (attrs.getValue(argNamePrefix + idx) != null) { 588 args.add(decodeBase64(attrs.getValue(argNamePrefix + idx))); 589 idx++; 590 } 591 592 String paramNamePrefix = MF_JAVAFX_PARAMETER_NAME_PREFIX; 593 String paramValuePrefix = MF_JAVAFX_PARAMETER_VALUE_PREFIX; 594 idx = 1; 595 while (attrs.getValue(paramNamePrefix + idx) != null) { 596 String k = decodeBase64(attrs.getValue(paramNamePrefix + idx)); 597 String v = null; 598 if (attrs.getValue(paramValuePrefix + idx) != null) { 599 v = decodeBase64(attrs.getValue(paramValuePrefix + idx)); 600 } 601 args.add("--" + k + "=" + (v != null ? v : "")); 602 idx++; 603 } 604 } catch (IOException ioe) { 605 if (verbose) { 606 System.err.println("Failed to extract application parameters"); 607 } 608 ioe.printStackTrace(); 609 } 610 611 return (String[]) args.toArray(new String[0]); 612 } 613 614 // FIXME: needs localization, since these are presented to the user 615 private static void abort(final Throwable cause, final String fmt, final Object... args) { 616 String msg = String.format(fmt, args); 617 if (msg != null) { 618 System.err.println(msg); 619 } 620 621 if (trace) { 622 if (cause != null) { 623 cause.printStackTrace(); 624 } else { 625 Thread.dumpStack(); 626 } 627 } 628 System.exit(1); 629 } 630 631 private static Attributes getJarAttributes(String jarPath) { 632 JarFile jarFile = null; 633 try { 634 jarFile = new JarFile(jarPath); 635 Manifest manifest = jarFile.getManifest(); 636 if (manifest == null) { 637 abort(null, "No manifest in jar file %1$s", jarPath); 638 } 639 return manifest.getMainAttributes(); 640 } catch (IOException ioe) { 641 abort(ioe, "Error launching jar file %1%s", jarPath); 642 } finally { 643 try { 644 jarFile.close(); 645 } catch (IOException ioe) {} 646 } 647 return null; 648 } 649 650 private static void startToolkit() throws InterruptedException { 651 if (toolkitStarted.getAndSet(true)) { 652 return; 653 } 654 655 final CountDownLatch startupLatch = new CountDownLatch(1); 656 657 // Note, this method is called on the FX Application Thread 658 PlatformImpl.startup(() -> startupLatch.countDown()); 659 660 // Wait for FX platform to start 661 startupLatch.await(); 662 } 663 664 private static volatile boolean error = false; 665 private static volatile Throwable pConstructorError = null; 666 private static volatile Throwable pInitError = null; 667 private static volatile Throwable pStartError = null; 668 private static volatile Throwable pStopError = null; 669 private static volatile Throwable constructorError = null; 670 private static volatile Throwable initError = null; 671 private static volatile Throwable startError = null; 672 private static volatile Throwable stopError = null; 673 674 private static void launchApplication1(final Class<? extends Application> appClass, 675 final Class<? extends Preloader> preloaderClass, 676 final String[] args) throws Exception { 677 678 startToolkit(); 679 680 if (savedMainCcl != null) { 681 /* 682 * The toolkit was already started by the java launcher, and the 683 * main method of the application class was called. Check to see 684 * whether the CCL has been changed. If so, then we need 685 * to pass the context class loader to the FX app thread so that it 686 * correctly picks up the current setting. 687 */ 688 final ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 689 if (ccl != null && ccl != savedMainCcl) { 690 PlatformImpl.runLater(() -> { 691 Thread.currentThread().setContextClassLoader(ccl); 692 }); 693 } 694 } 695 696 final AtomicBoolean pStartCalled = new AtomicBoolean(false); 697 final AtomicBoolean startCalled = new AtomicBoolean(false); 698 final AtomicBoolean exitCalled = new AtomicBoolean(false); 699 final AtomicBoolean pExitCalled = new AtomicBoolean(false); 700 final CountDownLatch shutdownLatch = new CountDownLatch(1); 701 final CountDownLatch pShutdownLatch = new CountDownLatch(1); 702 703 final PlatformImpl.FinishListener listener = new PlatformImpl.FinishListener() { 704 @Override public void idle(boolean implicitExit) { 705 if (!implicitExit) { 706 return; 707 } 708 709 // System.err.println("JavaFX Launcher: system is idle"); 710 if (startCalled.get()) { 711 shutdownLatch.countDown(); 712 } else if (pStartCalled.get()) { 713 pShutdownLatch.countDown(); 714 } 715 } 716 717 @Override public void exitCalled() { 718 // System.err.println("JavaFX Launcher: received exit notification"); 719 exitCalled.set(true); 720 shutdownLatch.countDown(); 721 } 722 }; 723 PlatformImpl.addListener(listener); 724 725 try { 726 final AtomicReference<Preloader> pldr = new AtomicReference<>(); 727 if (preloaderClass != null) { 728 // Construct an instance of the preloader on the FX thread, then 729 // call its init method on this (launcher) thread. Then call 730 // the start method on the FX thread. 731 PlatformImpl.runAndWait(() -> { 732 try { 733 Constructor<? extends Preloader> c = preloaderClass.getConstructor(); 734 pldr.set(c.newInstance()); 735 // Set startup parameters 736 ParametersImpl.registerParameters(pldr.get(), new ParametersImpl(args)); 737 } catch (Throwable t) { 738 System.err.println("Exception in Preloader constructor"); 739 pConstructorError = t; 740 error = true; 741 } 742 }); 743 } 744 currentPreloader = pldr.get(); 745 746 // Call init method unless exit called or error detected 747 if (currentPreloader != null && !error && !exitCalled.get()) { 748 try { 749 // Call the application init method (on the Launcher thread) 750 currentPreloader.init(); 751 } catch (Throwable t) { 752 System.err.println("Exception in Preloader init method"); 753 pInitError = t; 754 error = true; 755 } 756 } 757 758 // Call start method unless exit called or error detected 759 if (currentPreloader != null && !error && !exitCalled.get()) { 760 // Call the application start method on FX thread 761 PlatformImpl.runAndWait(() -> { 762 try { 763 pStartCalled.set(true); 764 765 // Create primary stage and call preloader start method 766 final Stage primaryStage = new Stage(); 767 StageHelper.setPrimary(primaryStage, true); 768 currentPreloader.start(primaryStage); 769 } catch (Throwable t) { 770 System.err.println("Exception in Preloader start method"); 771 pStartError = t; 772 error = true; 773 } 774 }); 775 776 // Notify preloader of progress 777 if (!error && !exitCalled.get()) { 778 notifyProgress(currentPreloader, 0.0); 779 } 780 } 781 782 // Construct an instance of the application on the FX thread, then 783 // call its init method on this (launcher) thread. Then call 784 // the start method on the FX thread. 785 final AtomicReference<Application> app = new AtomicReference<>(); 786 if (!error && !exitCalled.get()) { 787 if (currentPreloader != null) { 788 if (simulateSlowProgress) { 789 for (int i = 0; i < 100; i++) { 790 notifyProgress(currentPreloader, (double)i / 100.0); 791 Thread.sleep(10); 792 } 793 } 794 notifyProgress(currentPreloader, 1.0); 795 notifyStateChange(currentPreloader, 796 StateChangeNotification.Type.BEFORE_LOAD, null); 797 } 798 799 PlatformImpl.runAndWait(() -> { 800 try { 801 Constructor<? extends Application> c = appClass.getConstructor(); 802 app.set(c.newInstance()); 803 // Set startup parameters 804 ParametersImpl.registerParameters(app.get(), new ParametersImpl(args)); 805 PlatformImpl.setApplicationName(appClass); 806 } catch (Throwable t) { 807 System.err.println("Exception in Application constructor"); 808 constructorError = t; 809 error = true; 810 } 811 }); 812 } 813 final Application theApp = app.get(); 814 815 // Call init method unless exit called or error detected 816 if (!error && !exitCalled.get()) { 817 if (currentPreloader != null) { 818 notifyStateChange(currentPreloader, 819 StateChangeNotification.Type.BEFORE_INIT, theApp); 820 } 821 822 try { 823 // Call the application init method (on the Launcher thread) 824 theApp.init(); 825 } catch (Throwable t) { 826 System.err.println("Exception in Application init method"); 827 initError = t; 828 error = true; 829 } 830 } 831 832 // Call start method unless exit called or error detected 833 if (!error && !exitCalled.get()) { 834 if (currentPreloader != null) { 835 notifyStateChange(currentPreloader, 836 StateChangeNotification.Type.BEFORE_START, theApp); 837 } 838 // Call the application start method on FX thread 839 PlatformImpl.runAndWait(() -> { 840 try { 841 startCalled.set(true); 842 843 // Create primary stage and call application start method 844 final Stage primaryStage = new Stage(); 845 StageHelper.setPrimary(primaryStage, true); 846 theApp.start(primaryStage); 847 } catch (Throwable t) { 848 System.err.println("Exception in Application start method"); 849 startError = t; 850 error = true; 851 } 852 }); 853 } 854 855 if (!error) { 856 shutdownLatch.await(); 857 // System.err.println("JavaFX Launcher: time to call stop"); 858 } 859 860 // Call stop method if start was called 861 if (startCalled.get()) { 862 // Call Application stop method on FX thread 863 PlatformImpl.runAndWait(() -> { 864 try { 865 theApp.stop(); 866 } catch (Throwable t) { 867 System.err.println("Exception in Application stop method"); 868 stopError = t; 869 error = true; 870 } 871 }); 872 } 873 874 if (error) { 875 if (pConstructorError != null) { 876 throw new RuntimeException("Unable to construct Preloader instance: " 877 + appClass, pConstructorError); 878 } else if (pInitError != null) { 879 throw new RuntimeException("Exception in Preloader init method", 880 pInitError); 881 } else if(pStartError != null) { 882 throw new RuntimeException("Exception in Preloader start method", 883 pStartError); 884 } else if (pStopError != null) { 885 throw new RuntimeException("Exception in Preloader stop method", 886 pStopError); 887 } else if (constructorError != null) { 888 String msg = "Unable to construct Application instance: " + appClass; 889 if (!notifyError(msg, constructorError)) { 890 throw new RuntimeException(msg, constructorError); 891 } 892 } else if (initError != null) { 893 String msg = "Exception in Application init method"; 894 if (!notifyError(msg, initError)) { 895 throw new RuntimeException(msg, initError); 896 } 897 } else if(startError != null) { 898 String msg = "Exception in Application start method"; 899 if (!notifyError(msg, startError)) { 900 throw new RuntimeException(msg, startError); 901 } 902 } else if (stopError != null) { 903 String msg = "Exception in Application stop method"; 904 if (!notifyError(msg, stopError)) { 905 throw new RuntimeException(msg, stopError); 906 } 907 } 908 } 909 } finally { 910 PlatformImpl.removeListener(listener); 911 PlatformImpl.tkExit(); 912 } 913 } 914 915 private static void notifyStateChange(final Preloader preloader, 916 final StateChangeNotification.Type type, 917 final Application app) { 918 919 PlatformImpl.runAndWait(() -> preloader.handleStateChangeNotification( 920 new StateChangeNotification(type, app))); 921 } 922 923 private static void notifyProgress(final Preloader preloader, final double d) { 924 PlatformImpl.runAndWait(() -> preloader.handleProgressNotification( 925 new Preloader.ProgressNotification(d))); 926 } 927 928 private static boolean notifyError(final String msg, final Throwable constructorError) { 929 final AtomicBoolean result = new AtomicBoolean(false); 930 PlatformImpl.runAndWait(() -> { 931 if (currentPreloader != null) { 932 try { 933 ErrorNotification evt = new ErrorNotification(null, msg, constructorError); 934 boolean rval = currentPreloader.handleErrorNotification(evt); 935 result.set(rval); 936 } catch (Throwable t) { 937 t.printStackTrace(); 938 } 939 } 940 }); 941 942 return result.get(); 943 } 944 945 private static void notifyCurrentPreloader(final PreloaderNotification pe) { 946 PlatformImpl.runAndWait(() -> { 947 if (currentPreloader != null) { 948 currentPreloader.handleApplicationNotification(pe); 949 } 950 }); 951 } 952 953 private static Method notifyMethod = null; 954 955 public static void notifyPreloader(Application app, final PreloaderNotification info) { 956 if (launchCalled.get()) { 957 // Standalone launcher mode 958 notifyCurrentPreloader(info); 959 return; 960 } 961 } 962 963 // Not an instantiable class. 964 private LauncherImpl() { 965 // Should never get here. 966 throw new InternalError(); 967 } 968 969 }