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