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