LauncherHelper.java 28.0 KB
Newer Older
1
/*
2
 * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
3 4 5 6
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * 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 12 13 14 15 16 17 18 19 20
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
21 22 23
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
 */

package sun.launcher;

/*
 *
 *  <p><b>This is NOT part of any API supported by Sun Microsystems.
 *  If you write code that depends on this, you do so at your own
 *  risk.  This code and its internal interfaces are subject to change
 *  or deletion without notice.</b>
 *
 */

/**
 * A utility package for the java(1), javaw(1) launchers.
 * The following are helper methods that the native launcher uses
 * to perform checks etc. using JNI, see src/share/bin/java.c
 */
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
45
import java.io.UnsupportedEncodingException;
46 47
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
48 49
import java.math.BigDecimal;
import java.math.RoundingMode;
50
import java.nio.charset.Charset;
51 52 53
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
54 55
import java.util.ResourceBundle;
import java.text.MessageFormat;
56 57
import java.util.ArrayList;
import java.util.Collections;
K
ksrini 已提交
58
import java.util.Iterator;
59 60
import java.util.List;
import java.util.Locale;
K
ksrini 已提交
61
import java.util.Locale.Category;
62
import java.util.Properties;
K
ksrini 已提交
63 64
import java.util.Set;
import java.util.TreeSet;
65 66 67 68 69 70 71 72 73
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

public enum LauncherHelper {
    INSTANCE;
    private static final String MAIN_CLASS = "Main-Class";
    private static StringBuilder outBuf = new StringBuilder();

74 75 76 77 78
    private static final String INDENT = "    ";
    private static final String VM_SETTINGS     = "VM settings:";
    private static final String PROP_SETTINGS   = "Property settings:";
    private static final String LOCALE_SETTINGS = "Locale settings:";

79 80
    // sync with java.c and sun.misc.VM
    private static final String diagprop = "sun.java.launcher.diag";
81
    final static boolean trace = sun.misc.VM.getSavedProperty(diagprop) != null;
82 83 84 85 86 87

    private static final String defaultBundleName =
            "sun.launcher.resources.launcher";
    private static class ResourceBundleHolder {
        private static final ResourceBundle RB =
                ResourceBundle.getBundle(defaultBundleName);
88
    }
89 90 91
    private static PrintStream ostream;
    private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
    private static Class<?> appClass; // application class, for GUI/reporting purposes
92

93 94 95 96 97 98 99 100 101 102 103
    /*
     * A method called by the launcher to print out the standard settings,
     * by default -XshowSettings is equivalent to -XshowSettings:all,
     * Specific information may be gotten by using suboptions with possible
     * values vm, properties and locale.
     *
     * printToStderr: choose between stdout and stderr
     *
     * optionFlag: specifies which options to print default is all other
     *    possible values are vm, properties, locale.
     *
K
ksrini 已提交
104 105 106 107
     * initialHeapSize: in bytes, as set by the launcher, a zero-value indicates
     *    this code should determine this value, using a suitable method or
     *    the line could be omitted.
     *
108 109 110 111
     * maxHeapSize: in bytes, as set by the launcher, a zero-value indicates
     *    this code should determine this value, using a suitable method.
     *
     * stackSize: in bytes, as set by the launcher, a zero-value indicates
K
ksrini 已提交
112 113
     *    this code determine this value, using a suitable method or omit the
     *    line entirely.
114 115
     */
    static void showSettings(boolean printToStderr, String optionFlag,
K
ksrini 已提交
116 117
            long initialHeapSize, long maxHeapSize, long stackSize,
            boolean isServer) {
118

119
        initOutput(printToStderr);
120 121 122 123 124 125
        String opts[] = optionFlag.split(":");
        String optStr = (opts.length > 1 && opts[1] != null)
                ? opts[1].trim()
                : "all";
        switch (optStr) {
            case "vm":
126 127
                printVmSettings(initialHeapSize, maxHeapSize,
                                stackSize, isServer);
128 129
                break;
            case "properties":
130
                printProperties();
131 132
                break;
            case "locale":
133
                printLocale();
134 135
                break;
            default:
136 137 138 139
                printVmSettings(initialHeapSize, maxHeapSize, stackSize,
                                isServer);
                printProperties();
                printLocale();
140 141 142 143 144 145 146
                break;
        }
    }

    /*
     * prints the main vm settings subopt/section
     */
147
    private static void printVmSettings(
K
ksrini 已提交
148
            long initialHeapSize, long maxHeapSize,
149 150 151 152
            long stackSize, boolean isServer) {

        ostream.println(VM_SETTINGS);
        if (stackSize != 0L) {
K
ksrini 已提交
153 154 155 156 157 158
            ostream.println(INDENT + "Stack Size: " +
                    SizePrefix.scaleValue(stackSize));
        }
        if (initialHeapSize != 0L) {
             ostream.println(INDENT + "Min. Heap Size: " +
                    SizePrefix.scaleValue(initialHeapSize));
159 160
        }
        if (maxHeapSize != 0L) {
K
ksrini 已提交
161 162
            ostream.println(INDENT + "Max. Heap Size: " +
                    SizePrefix.scaleValue(maxHeapSize));
163 164
        } else {
            ostream.println(INDENT + "Max. Heap Size (Estimated): "
K
ksrini 已提交
165
                    + SizePrefix.scaleValue(Runtime.getRuntime().maxMemory()));
166 167 168 169 170 171 172 173 174 175 176
        }
        ostream.println(INDENT + "Ergonomics Machine Class: "
                + ((isServer) ? "server" : "client"));
        ostream.println(INDENT + "Using VM: "
                + System.getProperty("java.vm.name"));
        ostream.println();
    }

    /*
     * prints the properties subopt/section
     */
177
    private static void printProperties() {
178 179 180 181 182 183
        Properties p = System.getProperties();
        ostream.println(PROP_SETTINGS);
        List<String> sortedPropertyKeys = new ArrayList<>();
        sortedPropertyKeys.addAll(p.stringPropertyNames());
        Collections.sort(sortedPropertyKeys);
        for (String x : sortedPropertyKeys) {
184
            printPropertyValue(x, p.getProperty(x));
185 186 187 188 189 190 191 192
        }
        ostream.println();
    }

    private static boolean isPath(String key) {
        return key.endsWith(".dirs") || key.endsWith(".path");
    }

193
    private static void printPropertyValue(String key, String value) {
194 195
        ostream.print(INDENT + key + " = ");
        if (key.equals("line.separator")) {
K
ksrini 已提交
196
            for (byte b : value.getBytes()) {
197 198
                switch (b) {
                    case 0xd:
K
ksrini 已提交
199
                        ostream.print("\\r ");
200 201
                        break;
                    case 0xa:
K
ksrini 已提交
202
                        ostream.print("\\n ");
203 204
                        break;
                    default:
K
ksrini 已提交
205 206
                        // print any bizzare line separators in hex, but really
                        // shouldn't happen.
207 208 209 210 211 212 213 214 215 216 217 218
                        ostream.printf("0x%02X", b & 0xff);
                        break;
                }
            }
            ostream.println();
            return;
        }
        if (!isPath(key)) {
            ostream.println(value);
            return;
        }
        String[] values = value.split(System.getProperty("path.separator"));
K
ksrini 已提交
219 220 221 222 223
        boolean first = true;
        for (String s : values) {
            if (first) { // first line treated specially
                ostream.println(s);
                first = false;
224
            } else { // following lines prefix with indents
K
ksrini 已提交
225
                ostream.println(INDENT + INDENT + s);
226 227 228 229 230 231 232
            }
        }
    }

    /*
     * prints the locale subopt/section
     */
233
    private static void printLocale() {
234 235
        Locale locale = Locale.getDefault();
        ostream.println(LOCALE_SETTINGS);
K
ksrini 已提交
236 237 238 239 240 241
        ostream.println(INDENT + "default locale = " +
                locale.getDisplayLanguage());
        ostream.println(INDENT + "default display locale = " +
                Locale.getDefault(Category.DISPLAY).getDisplayName());
        ostream.println(INDENT + "default format locale = " +
                Locale.getDefault(Category.FORMAT).getDisplayName());
242
        printLocales();
243 244 245
        ostream.println();
    }

246
    private static void printLocales() {
K
ksrini 已提交
247 248
        Locale[] tlocales = Locale.getAvailableLocales();
        final int len = tlocales == null ? 0 : tlocales.length;
249 250 251
        if (len < 1 ) {
            return;
        }
K
ksrini 已提交
252 253 254 255 256 257 258
        // Locale does not implement Comparable so we convert it to String
        // and sort it for pretty printing.
        Set<String> sortedSet = new TreeSet<>();
        for (Locale l : tlocales) {
            sortedSet.add(l.toString());
        }

259
        ostream.print(INDENT + "available locales = ");
K
ksrini 已提交
260 261 262 263 264
        Iterator<String> iter = sortedSet.iterator();
        final int last = len - 1;
        for (int i = 0 ; iter.hasNext() ; i++) {
            String s = iter.next();
            ostream.print(s);
265 266 267 268 269 270 271 272 273
            if (i != last) {
                ostream.print(", ");
            }
            // print columns of 8
            if ((i + 1) % 8 == 0) {
                ostream.println();
                ostream.print(INDENT + INDENT);
            }
        }
K
ksrini 已提交
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
    }

    private enum SizePrefix {

        KILO(1024, "K"),
        MEGA(1024 * 1024, "M"),
        GIGA(1024 * 1024 * 1024, "G"),
        TERA(1024L * 1024L * 1024L * 1024L, "T");
        long size;
        String abbrev;

        SizePrefix(long size, String abbrev) {
            this.size = size;
            this.abbrev = abbrev;
        }

        private static String scale(long v, SizePrefix prefix) {
            return BigDecimal.valueOf(v).divide(BigDecimal.valueOf(prefix.size),
                    2, RoundingMode.HALF_EVEN).toPlainString() + prefix.abbrev;
        }
        /*
         * scale the incoming values to a human readable form, represented as
         * K, M, G and T, see java.c parse_size for the scaled values and
         * suffixes. The lowest possible scaled value is Kilo.
         */
        static String scaleValue(long v) {
            if (v < MEGA.size) {
                return scale(v, KILO);
            } else if (v < GIGA.size) {
                return scale(v, MEGA);
            } else if (v < TERA.size) {
                return scale(v, GIGA);
            } else {
                return scale(v, TERA);
            }
        }
310 311
    }

312 313 314 315 316
    /**
     * A private helper method to get a localized message and also
     * apply any arguments that we might pass.
     */
    private static String getLocalizedMessage(String key, Object... args) {
317
        String msg = ResourceBundleHolder.RB.getString(key);
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
        return (args != null) ? MessageFormat.format(msg, args) : msg;
    }

    /**
     * The java -help message is split into 3 parts, an invariant, followed
     * by a set of platform dependent variant messages, finally an invariant
     * set of lines.
     * This method initializes the help message for the first time, and also
     * assembles the invariant header part of the message.
     */
    static void initHelpMessage(String progname) {
        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.header",
                (progname == null) ? "java" : progname ));
        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.datamodel",
                32));
        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.datamodel",
                64));
    }

    /**
     * Appends the vm selection messages to the header, already created.
     * initHelpSystem must already be called.
     */
    static void appendVmSelectMessage(String vm1, String vm2) {
        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.vmselect",
                vm1, vm2));
    }

    /**
     * Appends the vm synoym message to the header, already created.
     * initHelpSystem must be called before using this method.
     */
    static void appendVmSynonymMessage(String vm1, String vm2) {
        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.hotspot",
                vm1, vm2));
    }

    /**
     * Appends the vm Ergo message to the header, already created.
     * initHelpSystem must be called before using this method.
     */
    static void appendVmErgoMessage(boolean isServerClass, String vm) {
        outBuf = outBuf.append(getLocalizedMessage("java.launcher.ergo.message1",
                vm));
        outBuf = (isServerClass)
             ? outBuf.append(",\n" +
                getLocalizedMessage("java.launcher.ergo.message2") + "\n\n")
             : outBuf.append(".\n\n");
    }

    /**
     * Appends the last invariant part to the previously created messages,
     * and finishes up the printing to the desired output stream.
     * initHelpSystem must be called before using this method.
     */
    static void printHelpMessage(boolean printToStderr) {
374
        initOutput(printToStderr);
375 376 377 378 379 380 381 382 383
        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.footer",
                File.pathSeparator));
        ostream.println(outBuf.toString());
    }

    /**
     * Prints the Xusage text to the desired output stream.
     */
    static void printXUsageMessage(boolean printToStderr) {
384
        initOutput(printToStderr);
385 386
        ostream.println(getLocalizedMessage("java.launcher.X.usage",
                File.pathSeparator));
387
        if (System.getProperty("os.name").contains("OS X")) {
388 389 390
            ostream.println(getLocalizedMessage("java.launcher.X.macosx.usage",
                        File.pathSeparator));
        }
391 392
    }

393 394 395 396 397 398 399 400 401 402
    static void initOutput(boolean printToStderr) {
        ostream =  (printToStderr) ? System.err : System.out;
    }

    static String getMainClassFromJar(String jarname) {
        String mainValue = null;
        try (JarFile jarFile = new JarFile(jarname)) {
            Manifest manifest = jarFile.getManifest();
            if (manifest == null) {
                abort(null, "java.launcher.jar.error2", jarname);
403
            }
404 405 406 407 408 409 410 411
            Attributes mainAttrs = manifest.getMainAttributes();
            if (mainAttrs == null) {
                abort(null, "java.launcher.jar.error3", jarname);
            }
            mainValue = mainAttrs.getValue(MAIN_CLASS);
            if (mainValue == null) {
                abort(null, "java.launcher.jar.error3", jarname);
            }
412 413 414 415 416 417 418 419 420
            /*
             * Hand off to FXHelper if it detects a JavaFX application
             * This must be done after ensuring a Main-Class entry
             * exists to enforce compliance with the jar specification
             */
            if (mainAttrs.containsKey(
                    new Attributes.Name(FXHelper.JAVAFX_APPLICATION_MARKER))) {
                return FXHelper.class.getName();
            }
421
            return mainValue.trim();
422
        } catch (IOException ioe) {
423
            abort(ioe, "java.launcher.jar.error1", jarname);
424
        }
425
        return null;
426 427
    }

428 429 430 431 432 433 434
    // From src/share/bin/java.c:
    //   enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR };

    private static final int LM_UNKNOWN = 0;
    private static final int LM_CLASS   = 1;
    private static final int LM_JAR     = 2;

435
    static void abort(Throwable t, String msgKey, Object... args) {
436 437 438
        if (msgKey != null) {
            ostream.println(getLocalizedMessage(msgKey, args));
        }
439
        if (trace) {
440 441 442
            if (t != null) {
                t.printStackTrace();
            } else {
443
                Thread.dumpStack();
444 445 446 447 448
            }
        }
        System.exit(1);
    }

449 450 451 452 453 454 455 456 457 458
    /**
     * This method does the following:
     * 1. gets the classname from a Jar's manifest, if necessary
     * 2. loads the class using the System ClassLoader
     * 3. ensures the availability and accessibility of the main method,
     *    using signatureDiagnostic method.
     *    a. does the class exist
     *    b. is there a main
     *    c. is the main public
     *    d. is the main static
459 460 461 462
     *    e. does the main take a String array for args
     * 4. if no main method and if the class extends FX Application, then call
     *    on FXHelper to determine the main class to launch
     * 5. and off we go......
463
     *
464 465 466 467 468 469
     * @param printToStderr if set, all output will be routed to stderr
     * @param mode LaunchMode as determined by the arguments passed on the
     * command line
     * @param what either the jar file to launch or the main class when using
     * LM_CLASS mode
     * @return the application's main class
470
     */
471 472
    public static Class<?> checkAndLoadMain(boolean printToStderr,
                                            int mode,
473
                                            String what) {
474
        initOutput(printToStderr);
475
        // get the class name
476 477
        String cn = null;
        switch (mode) {
478 479 480 481
            case LM_CLASS:
                cn = what;
                break;
            case LM_JAR:
482
                cn = getMainClassFromJar(what);
483 484 485 486
                break;
            default:
                // should never happen
                throw new InternalError("" + mode + ": Unknown launch mode");
487 488
        }
        cn = cn.replace('/', '.');
489
        Class<?> mainClass = null;
490
        try {
491 492 493
            mainClass = scloader.loadClass(cn);
        } catch (NoClassDefFoundError | ClassNotFoundException cnfe) {
            abort(cnfe, "java.launcher.cls.error1", cn);
494
        }
495
        // set to mainClass
496 497
        appClass = mainClass;

498 499 500 501 502 503 504 505 506 507
        /*
         * Check if FXHelper can launch it using the FX launcher. In an FX app,
         * the main class may or may not have a main method, so do this before
         * validating the main class.
         */
        if (mainClass.equals(FXHelper.class) ||
                FXHelper.doesExtendFXApplication(mainClass)) {
            // Will abort() if there are problems with the FX runtime
            FXHelper.setFXLaunchParameters(what, mode);
            return FXHelper.class;
508 509
        }

510 511
        validateMainClass(mainClass);
        return mainClass;
512 513 514 515 516 517 518 519 520 521
    }

    /*
     * Accessor method called by the launcher after getting the main class via
     * checkAndLoadMain(). The "application class" is the class that is finally
     * executed to start the application and in this case is used to report
     * the correct application name, typically for UI purposes.
     */
    public static Class<?> getApplicationClass() {
        return appClass;
522 523
    }

524 525 526
    // Check the existence and signature of main and abort if incorrect
    static void validateMainClass(Class<?> mainClass) {
        Method mainMethod;
527
        try {
528 529 530 531 532 533 534
            mainMethod = mainClass.getMethod("main", String[].class);
        } catch (NoSuchMethodException nsme) {
            // invalid main or not FX application, abort with an error
            abort(null, "java.launcher.cls.error4", mainClass.getName(),
                  FXHelper.JAVAFX_APPLICATION_CLASS_NAME);
            return; // Avoid compiler issues
        }
535

536
        /*
537 538 539
         * getMethod (above) will choose the correct method, based
         * on its name and parameter type, however, we still have to
         * ensure that the method is static and returns a void.
540
         */
541
        int mod = mainMethod.getModifiers();
542
        if (!Modifier.isStatic(mod)) {
543 544
            abort(null, "java.launcher.cls.error2", "static",
                  mainMethod.getDeclaringClass().getName());
545
        }
546 547 548
        if (mainMethod.getReturnType() != java.lang.Void.TYPE) {
            abort(null, "java.launcher.cls.error3",
                  mainMethod.getDeclaringClass().getName());
549
        }
550 551 552 553 554 555 556 557 558 559 560
    }

    private static final String encprop = "sun.jnu.encoding";
    private static String encoding = null;
    private static boolean isCharsetSupported = false;

    /*
     * converts a c or a byte array to a platform specific string,
     * previously implemented as a native method in the launcher.
     */
    static String makePlatformString(boolean printToStderr, byte[] inArray) {
561
        initOutput(printToStderr);
562 563 564 565 566 567 568 569 570 571
        if (encoding == null) {
            encoding = System.getProperty(encprop);
            isCharsetSupported = Charset.isSupported(encoding);
        }
        try {
            String out = isCharsetSupported
                    ? new String(inArray, encoding)
                    : new String(inArray);
            return out;
        } catch (UnsupportedEncodingException uee) {
572
            abort(uee, null);
573 574
        }
        return null; // keep the compiler happy
575
    }
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652

    static String[] expandArgs(String[] argArray) {
        List<StdArg> aList = new ArrayList<>();
        for (String x : argArray) {
            aList.add(new StdArg(x));
        }
        return expandArgs(aList);
    }

    static String[] expandArgs(List<StdArg> argList) {
        ArrayList<String> out = new ArrayList<>();
        if (trace) {
            System.err.println("Incoming arguments:");
        }
        for (StdArg a : argList) {
            if (trace) {
                System.err.println(a);
            }
            if (a.needsExpansion) {
                File x = new File(a.arg);
                File parent = x.getParentFile();
                String glob = x.getName();
                if (parent == null) {
                    parent = new File(".");
                }
                try (DirectoryStream<Path> dstream =
                        Files.newDirectoryStream(parent.toPath(), glob)) {
                    int entries = 0;
                    for (Path p : dstream) {
                        out.add(p.normalize().toString());
                        entries++;
                    }
                    if (entries == 0) {
                        out.add(a.arg);
                    }
                } catch (Exception e) {
                    out.add(a.arg);
                    if (trace) {
                        System.err.println("Warning: passing argument as-is " + a);
                        System.err.print(e);
                    }
                }
            } else {
                out.add(a.arg);
            }
        }
        String[] oarray = new String[out.size()];
        out.toArray(oarray);

        if (trace) {
            System.err.println("Expanded arguments:");
            for (String x : oarray) {
                System.err.println(x);
            }
        }
        return oarray;
    }

    /* duplicate of the native StdArg struct */
    private static class StdArg {
        final String arg;
        final boolean needsExpansion;
        StdArg(String arg, boolean expand) {
            this.arg = arg;
            this.needsExpansion = expand;
        }
        // protocol: first char indicates whether expansion is required
        // 'T' = true ; needs expansion
        // 'F' = false; needs no expansion
        StdArg(String in) {
            this.arg = in.substring(1);
            needsExpansion = in.charAt(0) == 'T';
        }
        public String toString() {
            return "StdArg{" + "arg=" + arg + ", needsExpansion=" + needsExpansion + '}';
        }
    }
653 654

    static final class FXHelper {
655 656 657
        // Marker entry in jar manifest that designates a JavaFX application jar
        private static final String JAVAFX_APPLICATION_MARKER =
                "JavaFX-Application-Class";
658 659 660 661 662
        private static final String JAVAFX_APPLICATION_CLASS_NAME =
                "javafx.application.Application";
        private static final String JAVAFX_LAUNCHER_CLASS_NAME =
                "com.sun.javafx.application.LauncherImpl";

663 664 665 666 667 668 669 670 671 672 673 674 675
        /*
         * The launch method used to invoke the JavaFX launcher. These must
         * match the strings used in the launchApplication method.
         *
         * Command line                 JavaFX-App-Class  Launch mode  FX Launch mode
         * java -cp fxapp.jar FXClass   N/A               LM_CLASS     "LM_CLASS"
         * java -cp somedir FXClass     N/A               LM_CLASS     "LM_CLASS"
         * java -jar fxapp.jar          Present           LM_JAR       "LM_JAR"
         * java -jar fxapp.jar          Not Present       LM_JAR       "LM_JAR"
         */
        private static final String JAVAFX_LAUNCH_MODE_CLASS = "LM_CLASS";
        private static final String JAVAFX_LAUNCH_MODE_JAR = "LM_JAR";

676 677 678 679
        /*
         * FX application launcher and launch method, so we can launch
         * applications with no main method.
         */
680 681 682
        private static String fxLaunchName = null;
        private static String fxLaunchMode = null;

683 684 685 686
        private static Class<?> fxLauncherClass    = null;
        private static Method   fxLauncherMethod   = null;

        /*
687 688 689
         * Set the launch params according to what was passed to LauncherHelper
         * so we can use the same launch mode for FX. Abort if there is any
         * issue with loading the FX runtime or with the launcher method.
690
         */
691
        private static void setFXLaunchParameters(String what, int mode) {
692 693 694
            // Check for the FX launcher classes
            try {
                fxLauncherClass = scloader.loadClass(JAVAFX_LAUNCHER_CLASS_NAME);
695 696 697 698 699
                /*
                 * signature must be:
                 * public static void launchApplication(String launchName,
                 *     String launchMode, String[] args);
                 */
700
                fxLauncherMethod = fxLauncherClass.getMethod("launchApplication",
701 702 703 704 705 706 707 708 709 710
                        String.class, String.class, String[].class);

                // verify launcher signature as we do when validating the main method
                int mod = fxLauncherMethod.getModifiers();
                if (!Modifier.isStatic(mod)) {
                    abort(null, "java.launcher.javafx.error1");
                }
                if (fxLauncherMethod.getReturnType() != java.lang.Void.TYPE) {
                    abort(null, "java.launcher.javafx.error1");
                }
711 712 713 714
            } catch (ClassNotFoundException | NoSuchMethodException ex) {
                abort(ex, "java.launcher.cls.error5", ex);
            }

715 716 717 718 719 720 721 722 723 724 725 726
            fxLaunchName = what;
            switch (mode) {
                case LM_CLASS:
                    fxLaunchMode = JAVAFX_LAUNCH_MODE_CLASS;
                    break;
                case LM_JAR:
                    fxLaunchMode = JAVAFX_LAUNCH_MODE_JAR;
                    break;
                default:
                    // should not have gotten this far...
                    throw new InternalError(mode + ": Unknown launch mode");
            }
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744
        }

        /*
         * Check if the given class is a JavaFX Application class. This is done
         * in a way that does not cause the Application class to load or throw
         * ClassNotFoundException if the JavaFX runtime is not available.
         */
        private static boolean doesExtendFXApplication(Class<?> mainClass) {
            for (Class<?> sc = mainClass.getSuperclass(); sc != null;
                    sc = sc.getSuperclass()) {
                if (sc.getName().equals(JAVAFX_APPLICATION_CLASS_NAME)) {
                    return true;
                }
            }
            return false;
        }

        public static void main(String... args) throws Exception {
745 746 747 748 749
            if (fxLauncherMethod == null
                    || fxLaunchMode == null
                    || fxLaunchName == null) {
                throw new RuntimeException("Invalid JavaFX launch parameters");
            }
750
            // launch appClass via fxLauncherMethod
751 752
            fxLauncherMethod.invoke(null,
                    new Object[] {fxLaunchName, fxLaunchMode, args});
753 754
        }
    }
755
}