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