LauncherHelper.java revision 12745:f068a4ffddd2
1/*
2 * Copyright (c) 2007, 2013, 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
26package sun.launcher;
27
28/*
29 *
30 *  <p><b>This is NOT part of any API supported by Sun Microsystems.
31 *  If you write code that depends on this, you do so at your own
32 *  risk.  This code and its internal interfaces are subject to change
33 *  or deletion without notice.</b>
34 *
35 */
36
37/**
38 * A utility package for the java(1), javaw(1) launchers.
39 * The following are helper methods that the native launcher uses
40 * to perform checks etc. using JNI, see src/share/bin/java.c
41 */
42import java.io.File;
43import java.io.IOException;
44import java.io.PrintStream;
45import java.io.UnsupportedEncodingException;
46import java.lang.reflect.Method;
47import java.lang.reflect.Modifier;
48import java.math.BigDecimal;
49import java.math.RoundingMode;
50import java.nio.charset.Charset;
51import java.nio.file.DirectoryStream;
52import java.nio.file.Files;
53import java.nio.file.Path;
54import java.text.Normalizer;
55import java.util.ResourceBundle;
56import java.text.MessageFormat;
57import java.util.ArrayList;
58import java.util.Collections;
59import java.util.Iterator;
60import java.util.List;
61import java.util.Locale;
62import java.util.Locale.Category;
63import java.util.Properties;
64import java.util.Set;
65import java.util.TreeSet;
66import java.util.jar.Attributes;
67import java.util.jar.JarFile;
68import java.util.jar.Manifest;
69
70public enum LauncherHelper {
71    INSTANCE;
72
73    // used to identify JavaFX applications
74    private static final String JAVAFX_APPLICATION_MARKER =
75            "JavaFX-Application-Class";
76    private static final String JAVAFX_APPLICATION_CLASS_NAME =
77            "javafx.application.Application";
78    private static final String JAVAFX_FXHELPER_CLASS_NAME_SUFFIX =
79            "sun.launcher.LauncherHelper$FXHelper";
80    private static final String MAIN_CLASS = "Main-Class";
81
82    private static StringBuilder outBuf = new StringBuilder();
83
84    private static final String INDENT = "    ";
85    private static final String VM_SETTINGS     = "VM settings:";
86    private static final String PROP_SETTINGS   = "Property settings:";
87    private static final String LOCALE_SETTINGS = "Locale settings:";
88
89    // sync with java.c and sun.misc.VM
90    private static final String diagprop = "sun.java.launcher.diag";
91    static final boolean trace = sun.misc.VM.getSavedProperty(diagprop) != null;
92
93    private static final String defaultBundleName =
94            "sun.launcher.resources.launcher";
95    private static class ResourceBundleHolder {
96        private static final ResourceBundle RB =
97                ResourceBundle.getBundle(defaultBundleName);
98    }
99    private static PrintStream ostream;
100    private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
101    private static Class<?> appClass; // application class, for GUI/reporting purposes
102
103    /*
104     * A method called by the launcher to print out the standard settings,
105     * by default -XshowSettings is equivalent to -XshowSettings:all,
106     * Specific information may be gotten by using suboptions with possible
107     * values vm, properties and locale.
108     *
109     * printToStderr: choose between stdout and stderr
110     *
111     * optionFlag: specifies which options to print default is all other
112     *    possible values are vm, properties, locale.
113     *
114     * initialHeapSize: in bytes, as set by the launcher, a zero-value indicates
115     *    this code should determine this value, using a suitable method or
116     *    the line could be omitted.
117     *
118     * maxHeapSize: in bytes, as set by the launcher, a zero-value indicates
119     *    this code should determine this value, using a suitable method.
120     *
121     * stackSize: in bytes, as set by the launcher, a zero-value indicates
122     *    this code determine this value, using a suitable method or omit the
123     *    line entirely.
124     */
125    static void showSettings(boolean printToStderr, String optionFlag,
126            long initialHeapSize, long maxHeapSize, long stackSize,
127            boolean isServer) {
128
129        initOutput(printToStderr);
130        String opts[] = optionFlag.split(":");
131        String optStr = (opts.length > 1 && opts[1] != null)
132                ? opts[1].trim()
133                : "all";
134        switch (optStr) {
135            case "vm":
136                printVmSettings(initialHeapSize, maxHeapSize,
137                                stackSize, isServer);
138                break;
139            case "properties":
140                printProperties();
141                break;
142            case "locale":
143                printLocale();
144                break;
145            default:
146                printVmSettings(initialHeapSize, maxHeapSize, stackSize,
147                                isServer);
148                printProperties();
149                printLocale();
150                break;
151        }
152    }
153
154    /*
155     * prints the main vm settings subopt/section
156     */
157    private static void printVmSettings(
158            long initialHeapSize, long maxHeapSize,
159            long stackSize, boolean isServer) {
160
161        ostream.println(VM_SETTINGS);
162        if (stackSize != 0L) {
163            ostream.println(INDENT + "Stack Size: " +
164                    SizePrefix.scaleValue(stackSize));
165        }
166        if (initialHeapSize != 0L) {
167             ostream.println(INDENT + "Min. Heap Size: " +
168                    SizePrefix.scaleValue(initialHeapSize));
169        }
170        if (maxHeapSize != 0L) {
171            ostream.println(INDENT + "Max. Heap Size: " +
172                    SizePrefix.scaleValue(maxHeapSize));
173        } else {
174            ostream.println(INDENT + "Max. Heap Size (Estimated): "
175                    + SizePrefix.scaleValue(Runtime.getRuntime().maxMemory()));
176        }
177        ostream.println(INDENT + "Ergonomics Machine Class: "
178                + ((isServer) ? "server" : "client"));
179        ostream.println(INDENT + "Using VM: "
180                + System.getProperty("java.vm.name"));
181        ostream.println();
182    }
183
184    /*
185     * prints the properties subopt/section
186     */
187    private static void printProperties() {
188        Properties p = System.getProperties();
189        ostream.println(PROP_SETTINGS);
190        List<String> sortedPropertyKeys = new ArrayList<>();
191        sortedPropertyKeys.addAll(p.stringPropertyNames());
192        Collections.sort(sortedPropertyKeys);
193        for (String x : sortedPropertyKeys) {
194            printPropertyValue(x, p.getProperty(x));
195        }
196        ostream.println();
197    }
198
199    private static boolean isPath(String key) {
200        return key.endsWith(".dirs") || key.endsWith(".path");
201    }
202
203    private static void printPropertyValue(String key, String value) {
204        ostream.print(INDENT + key + " = ");
205        if (key.equals("line.separator")) {
206            for (byte b : value.getBytes()) {
207                switch (b) {
208                    case 0xd:
209                        ostream.print("\\r ");
210                        break;
211                    case 0xa:
212                        ostream.print("\\n ");
213                        break;
214                    default:
215                        // print any bizzare line separators in hex, but really
216                        // shouldn't happen.
217                        ostream.printf("0x%02X", b & 0xff);
218                        break;
219                }
220            }
221            ostream.println();
222            return;
223        }
224        if (!isPath(key)) {
225            ostream.println(value);
226            return;
227        }
228        String[] values = value.split(System.getProperty("path.separator"));
229        boolean first = true;
230        for (String s : values) {
231            if (first) { // first line treated specially
232                ostream.println(s);
233                first = false;
234            } else { // following lines prefix with indents
235                ostream.println(INDENT + INDENT + s);
236            }
237        }
238    }
239
240    /*
241     * prints the locale subopt/section
242     */
243    private static void printLocale() {
244        Locale locale = Locale.getDefault();
245        ostream.println(LOCALE_SETTINGS);
246        ostream.println(INDENT + "default locale = " +
247                locale.getDisplayLanguage());
248        ostream.println(INDENT + "default display locale = " +
249                Locale.getDefault(Category.DISPLAY).getDisplayName());
250        ostream.println(INDENT + "default format locale = " +
251                Locale.getDefault(Category.FORMAT).getDisplayName());
252        printLocales();
253        ostream.println();
254    }
255
256    private static void printLocales() {
257        Locale[] tlocales = Locale.getAvailableLocales();
258        final int len = tlocales == null ? 0 : tlocales.length;
259        if (len < 1 ) {
260            return;
261        }
262        // Locale does not implement Comparable so we convert it to String
263        // and sort it for pretty printing.
264        Set<String> sortedSet = new TreeSet<>();
265        for (Locale l : tlocales) {
266            sortedSet.add(l.toString());
267        }
268
269        ostream.print(INDENT + "available locales = ");
270        Iterator<String> iter = sortedSet.iterator();
271        final int last = len - 1;
272        for (int i = 0 ; iter.hasNext() ; i++) {
273            String s = iter.next();
274            ostream.print(s);
275            if (i != last) {
276                ostream.print(", ");
277            }
278            // print columns of 8
279            if ((i + 1) % 8 == 0) {
280                ostream.println();
281                ostream.print(INDENT + INDENT);
282            }
283        }
284    }
285
286    private enum SizePrefix {
287
288        KILO(1024, "K"),
289        MEGA(1024 * 1024, "M"),
290        GIGA(1024 * 1024 * 1024, "G"),
291        TERA(1024L * 1024L * 1024L * 1024L, "T");
292        long size;
293        String abbrev;
294
295        SizePrefix(long size, String abbrev) {
296            this.size = size;
297            this.abbrev = abbrev;
298        }
299
300        private static String scale(long v, SizePrefix prefix) {
301            return BigDecimal.valueOf(v).divide(BigDecimal.valueOf(prefix.size),
302                    2, RoundingMode.HALF_EVEN).toPlainString() + prefix.abbrev;
303        }
304        /*
305         * scale the incoming values to a human readable form, represented as
306         * K, M, G and T, see java.c parse_size for the scaled values and
307         * suffixes. The lowest possible scaled value is Kilo.
308         */
309        static String scaleValue(long v) {
310            if (v < MEGA.size) {
311                return scale(v, KILO);
312            } else if (v < GIGA.size) {
313                return scale(v, MEGA);
314            } else if (v < TERA.size) {
315                return scale(v, GIGA);
316            } else {
317                return scale(v, TERA);
318            }
319        }
320    }
321
322    /**
323     * A private helper method to get a localized message and also
324     * apply any arguments that we might pass.
325     */
326    private static String getLocalizedMessage(String key, Object... args) {
327        String msg = ResourceBundleHolder.RB.getString(key);
328        return (args != null) ? MessageFormat.format(msg, args) : msg;
329    }
330
331    /**
332     * The java -help message is split into 3 parts, an invariant, followed
333     * by a set of platform dependent variant messages, finally an invariant
334     * set of lines.
335     * This method initializes the help message for the first time, and also
336     * assembles the invariant header part of the message.
337     */
338    static void initHelpMessage(String progname) {
339        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.header",
340                (progname == null) ? "java" : progname ));
341        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.datamodel",
342                32));
343        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.datamodel",
344                64));
345    }
346
347    /**
348     * Appends the vm selection messages to the header, already created.
349     * initHelpSystem must already be called.
350     */
351    static void appendVmSelectMessage(String vm1, String vm2) {
352        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.vmselect",
353                vm1, vm2));
354    }
355
356    /**
357     * Appends the vm synoym message to the header, already created.
358     * initHelpSystem must be called before using this method.
359     */
360    static void appendVmSynonymMessage(String vm1, String vm2) {
361        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.hotspot",
362                vm1, vm2));
363    }
364
365    /**
366     * Appends the vm Ergo message to the header, already created.
367     * initHelpSystem must be called before using this method.
368     */
369    static void appendVmErgoMessage(boolean isServerClass, String vm) {
370        outBuf = outBuf.append(getLocalizedMessage("java.launcher.ergo.message1",
371                vm));
372        outBuf = (isServerClass) ? outBuf.append(",\n")
373                .append(getLocalizedMessage("java.launcher.ergo.message2"))
374                .append("\n\n") : outBuf.append(".\n\n");
375    }
376
377    /**
378     * Appends the last invariant part to the previously created messages,
379     * and finishes up the printing to the desired output stream.
380     * initHelpSystem must be called before using this method.
381     */
382    static void printHelpMessage(boolean printToStderr) {
383        initOutput(printToStderr);
384        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.footer",
385                File.pathSeparator));
386        ostream.println(outBuf.toString());
387    }
388
389    /**
390     * Prints the Xusage text to the desired output stream.
391     */
392    static void printXUsageMessage(boolean printToStderr) {
393        initOutput(printToStderr);
394        ostream.println(getLocalizedMessage("java.launcher.X.usage",
395                File.pathSeparator));
396        if (System.getProperty("os.name").contains("OS X")) {
397            ostream.println(getLocalizedMessage("java.launcher.X.macosx.usage",
398                        File.pathSeparator));
399        }
400    }
401
402    static void initOutput(boolean printToStderr) {
403        ostream =  (printToStderr) ? System.err : System.out;
404    }
405
406    static String getMainClassFromJar(String jarname) {
407        String mainValue = null;
408        try (JarFile jarFile = new JarFile(jarname)) {
409            Manifest manifest = jarFile.getManifest();
410            if (manifest == null) {
411                abort(null, "java.launcher.jar.error2", jarname);
412            }
413            Attributes mainAttrs = manifest.getMainAttributes();
414            if (mainAttrs == null) {
415                abort(null, "java.launcher.jar.error3", jarname);
416            }
417            mainValue = mainAttrs.getValue(MAIN_CLASS);
418            if (mainValue == null) {
419                abort(null, "java.launcher.jar.error3", jarname);
420            }
421
422            /*
423             * Hand off to FXHelper if it detects a JavaFX application
424             * This must be done after ensuring a Main-Class entry
425             * exists to enforce compliance with the jar specification
426             */
427            if (mainAttrs.containsKey(
428                    new Attributes.Name(JAVAFX_APPLICATION_MARKER))) {
429                FXHelper.setFXLaunchParameters(jarname, LM_JAR);
430                return FXHelper.class.getName();
431            }
432
433            return mainValue.trim();
434        } catch (IOException ioe) {
435            abort(ioe, "java.launcher.jar.error1", jarname);
436        }
437        return null;
438    }
439
440    // From src/share/bin/java.c:
441    //   enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR };
442
443    private static final int LM_UNKNOWN = 0;
444    private static final int LM_CLASS   = 1;
445    private static final int LM_JAR     = 2;
446
447    static void abort(Throwable t, String msgKey, Object... args) {
448        if (msgKey != null) {
449            ostream.println(getLocalizedMessage(msgKey, args));
450        }
451        if (trace) {
452            if (t != null) {
453                t.printStackTrace();
454            } else {
455                Thread.dumpStack();
456            }
457        }
458        System.exit(1);
459    }
460
461    /**
462     * This method does the following:
463     * 1. gets the classname from a Jar's manifest, if necessary
464     * 2. loads the class using the System ClassLoader
465     * 3. ensures the availability and accessibility of the main method,
466     *    using signatureDiagnostic method.
467     *    a. does the class exist
468     *    b. is there a main
469     *    c. is the main public
470     *    d. is the main static
471     *    e. does the main take a String array for args
472     * 4. if no main method and if the class extends FX Application, then call
473     *    on FXHelper to determine the main class to launch
474     * 5. and off we go......
475     *
476     * @param printToStderr if set, all output will be routed to stderr
477     * @param mode LaunchMode as determined by the arguments passed on the
478     * command line
479     * @param what either the jar file to launch or the main class when using
480     * LM_CLASS mode
481     * @return the application's main class
482     */
483    public static Class<?> checkAndLoadMain(boolean printToStderr,
484                                            int mode,
485                                            String what) {
486        initOutput(printToStderr);
487        // get the class name
488        String cn = null;
489        switch (mode) {
490            case LM_CLASS:
491                cn = what;
492                break;
493            case LM_JAR:
494                cn = getMainClassFromJar(what);
495                break;
496            default:
497                // should never happen
498                throw new InternalError("" + mode + ": Unknown launch mode");
499        }
500        cn = cn.replace('/', '.');
501        Class<?> mainClass = null;
502        try {
503            mainClass = scloader.loadClass(cn);
504        } catch (NoClassDefFoundError | ClassNotFoundException cnfe) {
505            if (System.getProperty("os.name", "").contains("OS X")
506                && Normalizer.isNormalized(cn, Normalizer.Form.NFD)) {
507                try {
508                    // On Mac OS X since all names with diacretic symbols are given as decomposed it
509                    // is possible that main class name comes incorrectly from the command line
510                    // and we have to re-compose it
511                    mainClass = scloader.loadClass(Normalizer.normalize(cn, Normalizer.Form.NFC));
512                } catch (NoClassDefFoundError | ClassNotFoundException cnfe1) {
513                    abort(cnfe, "java.launcher.cls.error1", cn);
514                }
515            } else {
516                abort(cnfe, "java.launcher.cls.error1", cn);
517            }
518        }
519        // set to mainClass
520        appClass = mainClass;
521
522        /*
523         * Check if FXHelper can launch it using the FX launcher. In an FX app,
524         * the main class may or may not have a main method, so do this before
525         * validating the main class.
526         */
527        if (JAVAFX_FXHELPER_CLASS_NAME_SUFFIX.equals(mainClass.getName()) ||
528            doesExtendFXApplication(mainClass)) {
529            // Will abort() if there are problems with FX runtime
530            FXHelper.setFXLaunchParameters(what, mode);
531            return FXHelper.class;
532        }
533
534        validateMainClass(mainClass);
535        return mainClass;
536    }
537
538    /*
539     * Accessor method called by the launcher after getting the main class via
540     * checkAndLoadMain(). The "application class" is the class that is finally
541     * executed to start the application and in this case is used to report
542     * the correct application name, typically for UI purposes.
543     */
544    public static Class<?> getApplicationClass() {
545        return appClass;
546    }
547
548    /*
549     * Check if the given class is a JavaFX Application class. This is done
550     * in a way that does not cause the Application class to load or throw
551     * ClassNotFoundException if the JavaFX runtime is not available.
552     */
553    private static boolean doesExtendFXApplication(Class<?> mainClass) {
554        for (Class<?> sc = mainClass.getSuperclass(); sc != null;
555                sc = sc.getSuperclass()) {
556            if (sc.getName().equals(JAVAFX_APPLICATION_CLASS_NAME)) {
557                return true;
558            }
559        }
560        return false;
561    }
562
563    // Check the existence and signature of main and abort if incorrect
564    static void validateMainClass(Class<?> mainClass) {
565        Method mainMethod;
566        try {
567            mainMethod = mainClass.getMethod("main", String[].class);
568        } catch (NoSuchMethodException nsme) {
569            // invalid main or not FX application, abort with an error
570            abort(null, "java.launcher.cls.error4", mainClass.getName(),
571                  JAVAFX_APPLICATION_CLASS_NAME);
572            return; // Avoid compiler issues
573        }
574
575        /*
576         * getMethod (above) will choose the correct method, based
577         * on its name and parameter type, however, we still have to
578         * ensure that the method is static and returns a void.
579         */
580        int mod = mainMethod.getModifiers();
581        if (!Modifier.isStatic(mod)) {
582            abort(null, "java.launcher.cls.error2", "static",
583                  mainMethod.getDeclaringClass().getName());
584        }
585        if (mainMethod.getReturnType() != java.lang.Void.TYPE) {
586            abort(null, "java.launcher.cls.error3",
587                  mainMethod.getDeclaringClass().getName());
588        }
589    }
590
591    private static final String encprop = "sun.jnu.encoding";
592    private static String encoding = null;
593    private static boolean isCharsetSupported = false;
594
595    /*
596     * converts a c or a byte array to a platform specific string,
597     * previously implemented as a native method in the launcher.
598     */
599    static String makePlatformString(boolean printToStderr, byte[] inArray) {
600        initOutput(printToStderr);
601        if (encoding == null) {
602            encoding = System.getProperty(encprop);
603            isCharsetSupported = Charset.isSupported(encoding);
604        }
605        try {
606            String out = isCharsetSupported
607                    ? new String(inArray, encoding)
608                    : new String(inArray);
609            return out;
610        } catch (UnsupportedEncodingException uee) {
611            abort(uee, null);
612        }
613        return null; // keep the compiler happy
614    }
615
616    static String[] expandArgs(String[] argArray) {
617        List<StdArg> aList = new ArrayList<>();
618        for (String x : argArray) {
619            aList.add(new StdArg(x));
620        }
621        return expandArgs(aList);
622    }
623
624    static String[] expandArgs(List<StdArg> argList) {
625        ArrayList<String> out = new ArrayList<>();
626        if (trace) {
627            System.err.println("Incoming arguments:");
628        }
629        for (StdArg a : argList) {
630            if (trace) {
631                System.err.println(a);
632            }
633            if (a.needsExpansion) {
634                File x = new File(a.arg);
635                File parent = x.getParentFile();
636                String glob = x.getName();
637                if (parent == null) {
638                    parent = new File(".");
639                }
640                try (DirectoryStream<Path> dstream =
641                        Files.newDirectoryStream(parent.toPath(), glob)) {
642                    int entries = 0;
643                    for (Path p : dstream) {
644                        out.add(p.normalize().toString());
645                        entries++;
646                    }
647                    if (entries == 0) {
648                        out.add(a.arg);
649                    }
650                } catch (Exception e) {
651                    out.add(a.arg);
652                    if (trace) {
653                        System.err.println("Warning: passing argument as-is " + a);
654                        System.err.print(e);
655                    }
656                }
657            } else {
658                out.add(a.arg);
659            }
660        }
661        String[] oarray = new String[out.size()];
662        out.toArray(oarray);
663
664        if (trace) {
665            System.err.println("Expanded arguments:");
666            for (String x : oarray) {
667                System.err.println(x);
668            }
669        }
670        return oarray;
671    }
672
673    /* duplicate of the native StdArg struct */
674    private static class StdArg {
675        final String arg;
676        final boolean needsExpansion;
677        StdArg(String arg, boolean expand) {
678            this.arg = arg;
679            this.needsExpansion = expand;
680        }
681        // protocol: first char indicates whether expansion is required
682        // 'T' = true ; needs expansion
683        // 'F' = false; needs no expansion
684        StdArg(String in) {
685            this.arg = in.substring(1);
686            needsExpansion = in.charAt(0) == 'T';
687        }
688        public String toString() {
689            return "StdArg{" + "arg=" + arg + ", needsExpansion=" + needsExpansion + '}';
690        }
691    }
692
693    static final class FXHelper {
694
695        private static final String JAVAFX_LAUNCHER_CLASS_NAME =
696                "com.sun.javafx.application.LauncherImpl";
697
698        /*
699         * The launch method used to invoke the JavaFX launcher. These must
700         * match the strings used in the launchApplication method.
701         *
702         * Command line                 JavaFX-App-Class  Launch mode  FX Launch mode
703         * java -cp fxapp.jar FXClass   N/A               LM_CLASS     "LM_CLASS"
704         * java -cp somedir FXClass     N/A               LM_CLASS     "LM_CLASS"
705         * java -jar fxapp.jar          Present           LM_JAR       "LM_JAR"
706         * java -jar fxapp.jar          Not Present       LM_JAR       "LM_JAR"
707         */
708        private static final String JAVAFX_LAUNCH_MODE_CLASS = "LM_CLASS";
709        private static final String JAVAFX_LAUNCH_MODE_JAR = "LM_JAR";
710
711        /*
712         * FX application launcher and launch method, so we can launch
713         * applications with no main method.
714         */
715        private static String fxLaunchName = null;
716        private static String fxLaunchMode = null;
717
718        private static Class<?> fxLauncherClass    = null;
719        private static Method   fxLauncherMethod   = null;
720
721        /*
722         * Set the launch params according to what was passed to LauncherHelper
723         * so we can use the same launch mode for FX. Abort if there is any
724         * issue with loading the FX runtime or with the launcher method.
725         */
726        private static void setFXLaunchParameters(String what, int mode) {
727            // Check for the FX launcher classes
728            try {
729                fxLauncherClass = scloader.loadClass(JAVAFX_LAUNCHER_CLASS_NAME);
730                /*
731                 * signature must be:
732                 * public static void launchApplication(String launchName,
733                 *     String launchMode, String[] args);
734                 */
735                fxLauncherMethod = fxLauncherClass.getMethod("launchApplication",
736                        String.class, String.class, String[].class);
737
738                // verify launcher signature as we do when validating the main method
739                int mod = fxLauncherMethod.getModifiers();
740                if (!Modifier.isStatic(mod)) {
741                    abort(null, "java.launcher.javafx.error1");
742                }
743                if (fxLauncherMethod.getReturnType() != java.lang.Void.TYPE) {
744                    abort(null, "java.launcher.javafx.error1");
745                }
746            } catch (ClassNotFoundException | NoSuchMethodException ex) {
747                abort(ex, "java.launcher.cls.error5", ex);
748            }
749
750            fxLaunchName = what;
751            switch (mode) {
752                case LM_CLASS:
753                    fxLaunchMode = JAVAFX_LAUNCH_MODE_CLASS;
754                    break;
755                case LM_JAR:
756                    fxLaunchMode = JAVAFX_LAUNCH_MODE_JAR;
757                    break;
758                default:
759                    // should not have gotten this far...
760                    throw new InternalError(mode + ": Unknown launch mode");
761            }
762        }
763
764        public static void main(String... args) throws Exception {
765            if (fxLauncherMethod == null
766                    || fxLaunchMode == null
767                    || fxLaunchName == null) {
768                throw new RuntimeException("Invalid JavaFX launch parameters");
769            }
770            // launch appClass via fxLauncherMethod
771            fxLauncherMethod.invoke(null,
772                    new Object[] {fxLaunchName, fxLaunchMode, args});
773        }
774    }
775}
776