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