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