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