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