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 58 public class LauncherImpl { 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 } catch (InvocationTargetException ex) { 398 ex.printStackTrace(); 399 abort(null, "Exception running application %1$s", tempAppClass.getName()); 400 return; 401 } 402 403 // Verify appClass extends Application 404 if (!Application.class.isAssignableFrom(tempAppClass)) { 405 abort(theEx, "JavaFX application class %1$s does not extend javafx.application.Application", tempAppClass.getName()); 406 } 407 appClass = tempAppClass.asSubclass(Application.class); 408 409 if (verbose) { 410 System.err.println("Launching application directly"); 411 } 412 launchApplication(appClass, preClass, args); 413 } 414 415 private static URL fileToURL(File file) throws IOException { 416 return file.getCanonicalFile().toURI().toURL(); 417 } 418 419 private static ClassLoader setupJavaFXClassLoader(File appJar, String fxClassPath) { 420 try { 421 File baseDir = appJar.getParentFile(); 422 ArrayList jcpList = new ArrayList(); 423 424 // Add in the jars from the JavaFX-Class-Path entry 425 // TODO: should check current classpath for duplicate entries and ignore them 426 String cp = fxClassPath; 427 if (cp != null) { 428 // these paths are relative to baseDir, which should be the 429 // directory containing the app jar file 430 while (cp.length() > 0) { 431 int pathSepIdx = cp.indexOf(" "); 432 if (pathSepIdx < 0) { 433 String pathElem = cp; 434 File f = (baseDir == null) ? 435 new File(pathElem) : new File(baseDir, pathElem); 436 if (f.exists()) { 437 jcpList.add(fileToURL(f)); 438 } else if (verbose) { 439 System.err.println("Class Path entry \""+pathElem 440 +"\" does not exist, ignoring"); 441 } 442 break; 443 } else if (pathSepIdx > 0) { 444 String pathElem = cp.substring(0, pathSepIdx); 445 File f = (baseDir == null) ? 446 new File(pathElem) : new File(baseDir, pathElem); 447 if (f.exists()) { 448 jcpList.add(fileToURL(f)); 449 } else if (verbose) { 450 System.err.println("Class Path entry \""+pathElem 451 +"\" does not exist, ignoring"); 452 } 453 } 454 cp = cp.substring(pathSepIdx + 1); 455 } 456 } 457 458 // don't bother if there's nothing to add 459 if (!jcpList.isEmpty()) { 460 ArrayList<URL> urlList = new ArrayList<URL>(); 461 462 // prepend the existing classpath 463 // this will already have the app jar, so no need to worry about it 464 cp = System.getProperty("java.class.path"); 465 if (cp != null) { 466 while (cp.length() > 0) { 467 int pathSepIdx = cp.indexOf(File.pathSeparatorChar); 468 if (pathSepIdx < 0) { 469 String pathElem = cp; 470 urlList.add(fileToURL(new File(pathElem))); 471 break; 472 } else if (pathSepIdx > 0) { 473 String pathElem = cp.substring(0, pathSepIdx); 474 urlList.add(fileToURL(new File(pathElem))); 475 } 476 cp = cp.substring(pathSepIdx + 1); 477 } 478 } 479 480 // we have to add jfxrt.jar to the new class loader, or the app won't load 481 URL jfxRtURL = LauncherImpl.class.getProtectionDomain().getCodeSource().getLocation(); 482 urlList.add(jfxRtURL); 483 484 // and finally append the JavaFX-Class-Path entries 485 urlList.addAll(jcpList); 486 487 URL[] urls = (URL[])urlList.toArray(new URL[0]); 488 if (verbose) { 489 System.err.println("===== URL list"); 490 for (int i = 0; i < urls.length; i++) { 491 System.err.println("" + urls[i]); 492 } 493 System.err.println("====="); 494 } 495 return new URLClassLoader(urls, null); 496 } 497 } catch (Exception ex) { 498 if (trace) { 499 System.err.println("Exception creating JavaFX class loader: "+ex); 500 ex.printStackTrace(); 501 } 502 } 503 return null; 504 } 505 506 private static void trySetAutoProxy() { 507 // if explicit proxy settings are proxided we will skip autoproxy 508 // Note: we only check few most popular settings. 509 if (System.getProperty("http.proxyHost") != null 510 || System.getProperty("https.proxyHost") != null 511 || System.getProperty("ftp.proxyHost") != null 512 || System.getProperty("socksProxyHost") != null) { 513 if (verbose) { 514 System.out.println("Explicit proxy settings detected. Skip autoconfig."); 515 System.out.println(" http.proxyHost=" + System.getProperty("http.proxyHost")); 516 System.out.println(" https.proxyHost=" + System.getProperty("https.proxyHost")); 517 System.out.println(" ftp.proxyHost=" + System.getProperty("ftp.proxyHost")); 518 System.out.println(" socksProxyHost=" + System.getProperty("socksProxyHost")); 519 } 520 return; 521 } 522 if (System.getProperty("javafx.autoproxy.disable") != null) { 523 if (verbose) { 524 System.out.println("Disable autoproxy on request."); 525 } 526 return; 527 } 528 529 // grab deploy.jar 530 // Note that we don't need to keep deploy.jar in the JavaFX classloader 531 // it is only needed long enough to configure the proxy 532 String javaHome = System.getProperty("java.home"); 533 File jreLibDir = new File(javaHome, "lib"); 534 File deployJar = new File(jreLibDir, "deploy.jar"); 535 536 URL[] deployURLs; 537 try { 538 deployURLs = new URL[] { 539 deployJar.toURI().toURL() 540 }; 541 } catch (MalformedURLException ex) { 542 if (trace) { 543 System.err.println("Unable to build URL to deploy.jar: "+ex); 544 ex.printStackTrace(); 545 } 546 return; // give up setting proxy, usually silently 547 } 548 549 try { 550 URLClassLoader dcl = new URLClassLoader(deployURLs); 551 Class sm = Class.forName("com.sun.deploy.services.ServiceManager", 552 true, 553 dcl); 554 Class params[] = {Integer.TYPE}; 555 Method setservice = sm.getDeclaredMethod("setService", params); 556 String osname = System.getProperty("os.name"); 557 558 String servicename; 559 if (osname.startsWith("Win")) { 560 servicename = "STANDALONE_TIGER_WIN32"; 561 } else if (osname.contains("Mac")) { 562 servicename = "STANDALONE_TIGER_MACOSX"; 563 } else { 564 servicename = "STANDALONE_TIGER_UNIX"; 565 } 566 Object values[] = new Object[1]; 567 Class pt = Class.forName("com.sun.deploy.services.PlatformType", 568 true, 569 dcl); 570 values[0] = pt.getField(servicename).get(null); 571 setservice.invoke(null, values); 572 573 Class dps = Class.forName( 574 "com.sun.deploy.net.proxy.DeployProxySelector", 575 true, 576 dcl); 577 Method m = dps.getDeclaredMethod("reset", new Class[0]); 578 m.invoke(null, new Object[0]); 579 580 if (verbose) { 581 System.out.println("Autoconfig of proxy is completed."); 582 } 583 } catch (Exception e) { 584 if (verbose) { 585 System.err.println("Failed to autoconfig proxy due to "+e); 586 } 587 if (trace) { 588 e.printStackTrace(); 589 } 590 } 591 } 592 593 private static String decodeBase64(String inp) throws IOException { 594 return new String(Base64.getDecoder().decode(inp)); 595 } 596 597 private static String[] getAppArguments(Attributes attrs) { 598 List args = new LinkedList(); 599 600 try { 601 int idx = 1; 602 String argNamePrefix = MF_JAVAFX_ARGUMENT_PREFIX; 603 while (attrs.getValue(argNamePrefix + idx) != null) { 604 args.add(decodeBase64(attrs.getValue(argNamePrefix + idx))); 605 idx++; 606 } 607 608 String paramNamePrefix = MF_JAVAFX_PARAMETER_NAME_PREFIX; 609 String paramValuePrefix = MF_JAVAFX_PARAMETER_VALUE_PREFIX; 610 idx = 1; 611 while (attrs.getValue(paramNamePrefix + idx) != null) { 612 String k = decodeBase64(attrs.getValue(paramNamePrefix + idx)); 613 String v = null; 614 if (attrs.getValue(paramValuePrefix + idx) != null) { 615 v = decodeBase64(attrs.getValue(paramValuePrefix + idx)); 616 } 617 args.add("--" + k + "=" + (v != null ? v : "")); 618 idx++; 619 } 620 } catch (IOException ioe) { 621 if (verbose) { 622 System.err.println("Failed to extract application parameters"); 623 } 624 ioe.printStackTrace(); 625 } 626 627 return (String[]) args.toArray(new String[0]); 628 } 629 630 // FIXME: needs localization, since these are presented to the user 631 private static void abort(final Throwable cause, final String fmt, final Object... args) { 632 String msg = String.format(fmt, args); 633 if (msg != null) { 634 System.err.println(msg); 635 } 636 637 if (trace) { 638 if (cause != null) { 639 cause.printStackTrace(); 640 } else { 641 Thread.dumpStack(); 642 } 643 } 644 System.exit(1); 645 } 646 647 private static Attributes getJarAttributes(String jarPath) { 648 JarFile jarFile = null; 649 try { 650 jarFile = new JarFile(jarPath); 651 Manifest manifest = jarFile.getManifest(); 652 if (manifest == null) { 653 abort(null, "No manifest in jar file %1$s", jarPath); 654 } 655 return manifest.getMainAttributes(); 656 } catch (IOException ioe) { 657 abort(ioe, "Error launching jar file %1%s", jarPath); 658 } finally { 659 try { 660 jarFile.close(); 661 } catch (IOException ioe) {} 662 } 663 return null; 664 } 665 666 private static void startToolkit() throws InterruptedException { 667 if (toolkitStarted.getAndSet(true)) { 668 return; 669 } 670 671 if (SystemProperties.isDebug()) { 672 MXExtension.initializeIfAvailable(); 673 } 674 675 final CountDownLatch startupLatch = new CountDownLatch(1); 676 677 // Note, this method is called on the FX Application Thread 678 PlatformImpl.startup(() -> startupLatch.countDown()); 679 680 // Wait for FX platform to start 681 startupLatch.await(); 682 } 683 684 private static volatile boolean error = false; 685 private static volatile Throwable pConstructorError = null; 686 private static volatile Throwable pInitError = null; 687 private static volatile Throwable pStartError = null; 688 private static volatile Throwable pStopError = null; 689 private static volatile Throwable constructorError = null; 690 private static volatile Throwable initError = null; 691 private static volatile Throwable startError = null; 692 private static volatile Throwable stopError = null; 693 694 private static void launchApplication1(final Class<? extends Application> appClass, 695 final Class<? extends Preloader> preloaderClass, 696 final String[] args) throws Exception { 697 698 startToolkit(); 699 700 if (savedMainCcl != null) { 701 /* 702 * The toolkit was already started by the java launcher, and the 703 * main method of the application class was called. Check to see 704 * whether the CCL has been changed. If so, then we need 705 * to pass the context class loader to the FX app thread so that it 706 * correctly picks up the current setting. 707 */ 708 final ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 709 if (ccl != null && ccl != savedMainCcl) { 710 PlatformImpl.runLater(() -> { 711 Thread.currentThread().setContextClassLoader(ccl); 712 }); 713 } 714 } 715 716 final AtomicBoolean pStartCalled = new AtomicBoolean(false); 717 final AtomicBoolean startCalled = new AtomicBoolean(false); 718 final AtomicBoolean exitCalled = new AtomicBoolean(false); 719 final AtomicBoolean pExitCalled = new AtomicBoolean(false); 720 final CountDownLatch shutdownLatch = new CountDownLatch(1); 721 final CountDownLatch pShutdownLatch = new CountDownLatch(1); 722 723 final PlatformImpl.FinishListener listener = new PlatformImpl.FinishListener() { 724 @Override public void idle(boolean implicitExit) { 725 if (!implicitExit) { 726 return; 727 } 728 729 // System.err.println("JavaFX Launcher: system is idle"); 730 if (startCalled.get()) { 731 shutdownLatch.countDown(); 732 } else if (pStartCalled.get()) { 733 pShutdownLatch.countDown(); 734 } 735 } 736 737 @Override public void exitCalled() { 738 // System.err.println("JavaFX Launcher: received exit notification"); 739 exitCalled.set(true); 740 shutdownLatch.countDown(); 741 } 742 }; 743 PlatformImpl.addListener(listener); 744 745 try { 746 final AtomicReference<Preloader> pldr = new AtomicReference<>(); 747 if (preloaderClass != null) { 748 // Construct an instance of the preloader on the FX thread, then 749 // call its init method on this (launcher) thread. Then call 750 // the start method on the FX thread. 751 PlatformImpl.runAndWait(() -> { 752 try { 753 Constructor<? extends Preloader> c = preloaderClass.getConstructor(); 754 pldr.set(c.newInstance()); 755 // Set startup parameters 756 ParametersImpl.registerParameters(pldr.get(), new ParametersImpl(args)); 757 } catch (Throwable t) { 758 System.err.println("Exception in Preloader constructor"); 759 pConstructorError = t; 760 error = true; 761 } 762 }); 763 } 764 currentPreloader = pldr.get(); 765 766 // Call init method unless exit called or error detected 767 if (currentPreloader != null && !error && !exitCalled.get()) { 768 try { 769 // Call the application init method (on the Launcher thread) 770 currentPreloader.init(); 771 } catch (Throwable t) { 772 System.err.println("Exception in Preloader init method"); 773 pInitError = t; 774 error = true; 775 } 776 } 777 778 // Call start method unless exit called or error detected 779 if (currentPreloader != null && !error && !exitCalled.get()) { 780 // Call the application start method on FX thread 781 PlatformImpl.runAndWait(() -> { 782 try { 783 pStartCalled.set(true); 784 785 // Create primary stage and call preloader start method 786 final Stage primaryStage = new Stage(); 787 primaryStage.impl_setPrimary(true); 788 currentPreloader.start(primaryStage); 789 } catch (Throwable t) { 790 System.err.println("Exception in Preloader start method"); 791 pStartError = t; 792 error = true; 793 } 794 }); 795 796 // Notify preloader of progress 797 if (!error && !exitCalled.get()) { 798 notifyProgress(currentPreloader, 0.0); 799 } 800 } 801 802 // Construct an instance of the application on the FX thread, then 803 // call its init method on this (launcher) thread. Then call 804 // the start method on the FX thread. 805 final AtomicReference<Application> app = new AtomicReference<>(); 806 if (!error && !exitCalled.get()) { 807 if (currentPreloader != null) { 808 if (simulateSlowProgress) { 809 for (int i = 0; i < 100; i++) { 810 notifyProgress(currentPreloader, (double)i / 100.0); 811 Thread.sleep(10); 812 } 813 } 814 notifyProgress(currentPreloader, 1.0); 815 notifyStateChange(currentPreloader, 816 StateChangeNotification.Type.BEFORE_LOAD, null); 817 } 818 819 PlatformImpl.runAndWait(() -> { 820 try { 821 Constructor<? extends Application> c = appClass.getConstructor(); 822 app.set(c.newInstance()); 823 // Set startup parameters 824 ParametersImpl.registerParameters(app.get(), new ParametersImpl(args)); 825 PlatformImpl.setApplicationName(appClass); 826 } catch (Throwable t) { 827 System.err.println("Exception in Application constructor"); 828 constructorError = t; 829 error = true; 830 } 831 }); 832 } 833 final Application theApp = app.get(); 834 835 // Call init method unless exit called or error detected 836 if (!error && !exitCalled.get()) { 837 if (currentPreloader != null) { 838 notifyStateChange(currentPreloader, 839 StateChangeNotification.Type.BEFORE_INIT, theApp); 840 } 841 842 try { 843 // Call the application init method (on the Launcher thread) 844 theApp.init(); 845 } catch (Throwable t) { 846 System.err.println("Exception in Application init method"); 847 initError = t; 848 error = true; 849 } 850 } 851 852 // Call start method unless exit called or error detected 853 if (!error && !exitCalled.get()) { 854 if (currentPreloader != null) { 855 notifyStateChange(currentPreloader, 856 StateChangeNotification.Type.BEFORE_START, theApp); 857 } 858 // Call the application start method on FX thread 859 PlatformImpl.runAndWait(() -> { 860 try { 861 startCalled.set(true); 862 863 // Create primary stage and call application start method 864 final Stage primaryStage = new Stage(); 865 primaryStage.impl_setPrimary(true); 866 theApp.start(primaryStage); 867 } catch (Throwable t) { 868 System.err.println("Exception in Application start method"); 869 startError = t; 870 error = true; 871 } 872 }); 873 } 874 875 if (!error) { 876 shutdownLatch.await(); 877 // System.err.println("JavaFX Launcher: time to call stop"); 878 } 879 880 // Call stop method if start was called 881 if (startCalled.get()) { 882 // Call Application stop method on FX thread 883 PlatformImpl.runAndWait(() -> { 884 try { 885 theApp.stop(); 886 } catch (Throwable t) { 887 System.err.println("Exception in Application stop method"); 888 stopError = t; 889 error = true; 890 } 891 }); 892 } 893 894 if (error) { 895 if (pConstructorError != null) { 896 throw new RuntimeException("Unable to construct Preloader instance: " 897 + appClass, pConstructorError); 898 } else if (pInitError != null) { 899 throw new RuntimeException("Exception in Preloader init method", 900 pInitError); 901 } else if(pStartError != null) { 902 throw new RuntimeException("Exception in Preloader start method", 903 pStartError); 904 } else if (pStopError != null) { 905 throw new RuntimeException("Exception in Preloader stop method", 906 pStopError); 907 } else if (constructorError != null) { 908 String msg = "Unable to construct Application instance: " + appClass; 909 if (!notifyError(msg, constructorError)) { 910 throw new RuntimeException(msg, constructorError); 911 } 912 } else if (initError != null) { 913 String msg = "Exception in Application init method"; 914 if (!notifyError(msg, initError)) { 915 throw new RuntimeException(msg, initError); 916 } 917 } else if(startError != null) { 918 String msg = "Exception in Application start method"; 919 if (!notifyError(msg, startError)) { 920 throw new RuntimeException(msg, startError); 921 } 922 } else if (stopError != null) { 923 String msg = "Exception in Application stop method"; 924 if (!notifyError(msg, stopError)) { 925 throw new RuntimeException(msg, stopError); 926 } 927 } 928 } 929 } finally { 930 PlatformImpl.removeListener(listener); 931 // Workaround until RT-13281 is implemented 932 // Don't call exit if we detect an error in javaws mode 933 // PlatformImpl.tkExit(); 934 final boolean isJavaws = System.getSecurityManager() != null; 935 if (error && isJavaws) { 936 System.err.println("Workaround until RT-13281 is implemented: keep toolkit alive"); 937 } else { 938 PlatformImpl.tkExit(); 939 } 940 } 941 } 942 943 private static void notifyStateChange(final Preloader preloader, 944 final StateChangeNotification.Type type, 945 final Application app) { 946 947 PlatformImpl.runAndWait(() -> preloader.handleStateChangeNotification( 948 new StateChangeNotification(type, app))); 949 } 950 951 private static void notifyProgress(final Preloader preloader, final double d) { 952 PlatformImpl.runAndWait(() -> preloader.handleProgressNotification( 953 new Preloader.ProgressNotification(d))); 954 } 955 956 private static boolean notifyError(final String msg, final Throwable constructorError) { 957 final AtomicBoolean result = new AtomicBoolean(false); 958 PlatformImpl.runAndWait(() -> { 959 if (currentPreloader != null) { 960 try { 961 ErrorNotification evt = new ErrorNotification(null, msg, constructorError); 962 boolean rval = currentPreloader.handleErrorNotification(evt); 963 result.set(rval); 964 } catch (Throwable t) { 965 t.printStackTrace(); 966 } 967 } 968 }); 969 970 return result.get(); 971 } 972 973 private static void notifyCurrentPreloader(final PreloaderNotification pe) { 974 PlatformImpl.runAndWait(() -> { 975 if (currentPreloader != null) { 976 currentPreloader.handleApplicationNotification(pe); 977 } 978 }); 979 } 980 981 private static Method notifyMethod = null; 982 983 public static void notifyPreloader(Application app, final PreloaderNotification info) { 984 if (launchCalled.get()) { 985 // Standalone launcher mode 986 notifyCurrentPreloader(info); 987 return; 988 } 989 990 synchronized (LauncherImpl.class) { 991 if (notifyMethod == null) { 992 final String fxPreloaderClassName = 993 "com.sun.deploy.uitoolkit.impl.fx.FXPreloader"; 994 try { 995 Class fxPreloaderClass = Class.forName(fxPreloaderClassName); 996 notifyMethod = fxPreloaderClass.getMethod( 997 "notifyCurrentPreloader", PreloaderNotification.class); 998 } catch (Exception ex) { 999 ex.printStackTrace(); 1000 return; 1001 } 1002 } 1003 } 1004 1005 try { 1006 // Call using reflection: FXPreloader.notifyCurrentPreloader(pe) 1007 notifyMethod.invoke(null, info); 1008 } catch (Exception ex) { 1009 ex.printStackTrace(); 1010 } 1011 } 1012 1013 // Not an instantiable class. 1014 private LauncherImpl() { 1015 // Should never get here. 1016 throw new InternalError(); 1017 } 1018 1019 }