LauncherHelper.java revision 16177:89ef4b822745
1/*
2 * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
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.module.ModuleFinder;
47import java.lang.module.ModuleReference;
48import java.lang.module.ModuleDescriptor;
49import java.lang.module.ModuleDescriptor.Requires;
50import java.lang.module.ModuleDescriptor.Exports;
51import java.lang.module.ModuleDescriptor.Opens;
52import java.lang.module.ModuleDescriptor.Provides;
53import java.lang.reflect.Layer;
54import java.lang.reflect.Method;
55import java.lang.reflect.Modifier;
56import java.lang.reflect.Module;
57import java.math.BigDecimal;
58import java.math.RoundingMode;
59import java.net.URI;
60import java.nio.charset.Charset;
61import java.nio.file.DirectoryStream;
62import java.nio.file.Files;
63import java.nio.file.Path;
64import java.text.Normalizer;
65import java.text.MessageFormat;
66import java.util.ArrayList;
67import java.util.Collection;
68import java.util.Collections;
69import java.util.Comparator;
70import java.util.HashSet;
71import java.util.Iterator;
72import java.util.List;
73import java.util.Locale;
74import java.util.Locale.Category;
75import java.util.Map;
76import java.util.Optional;
77import java.util.Properties;
78import java.util.ResourceBundle;
79import java.util.Set;
80import java.util.TreeSet;
81import java.util.jar.Attributes;
82import java.util.jar.JarFile;
83import java.util.jar.Manifest;
84import java.util.stream.Collectors;
85import java.util.stream.Stream;
86
87import jdk.internal.misc.VM;
88import jdk.internal.module.Modules;
89
90
91public final class LauncherHelper {
92
93    // No instantiation
94    private LauncherHelper() {}
95
96    // used to identify JavaFX applications
97    private static final String JAVAFX_APPLICATION_MARKER =
98            "JavaFX-Application-Class";
99    private static final String JAVAFX_APPLICATION_CLASS_NAME =
100            "javafx.application.Application";
101    private static final String JAVAFX_FXHELPER_CLASS_NAME_SUFFIX =
102            "sun.launcher.LauncherHelper$FXHelper";
103    private static final String MAIN_CLASS = "Main-Class";
104    private static final String ADD_EXPORTS = "Add-Exports";
105    private static final String ADD_OPENS = "Add-Opens";
106
107    private static StringBuilder outBuf = new StringBuilder();
108
109    private static final String INDENT = "    ";
110    private static final String VM_SETTINGS     = "VM settings:";
111    private static final String PROP_SETTINGS   = "Property settings:";
112    private static final String LOCALE_SETTINGS = "Locale settings:";
113
114    // sync with java.c and jdk.internal.misc.VM
115    private static final String diagprop = "sun.java.launcher.diag";
116    static final boolean trace = VM.getSavedProperty(diagprop) != null;
117
118    private static final String defaultBundleName =
119            "sun.launcher.resources.launcher";
120    private static class ResourceBundleHolder {
121        private static final ResourceBundle RB =
122                ResourceBundle.getBundle(defaultBundleName);
123    }
124    private static PrintStream ostream;
125    private static Class<?> appClass; // application class, for GUI/reporting purposes
126
127    /*
128     * A method called by the launcher to print out the standard settings,
129     * by default -XshowSettings is equivalent to -XshowSettings:all,
130     * Specific information may be gotten by using suboptions with possible
131     * values vm, properties and locale.
132     *
133     * printToStderr: choose between stdout and stderr
134     *
135     * optionFlag: specifies which options to print default is all other
136     *    possible values are vm, properties, locale.
137     *
138     * initialHeapSize: in bytes, as set by the launcher, a zero-value indicates
139     *    this code should determine this value, using a suitable method or
140     *    the line could be omitted.
141     *
142     * maxHeapSize: in bytes, as set by the launcher, a zero-value indicates
143     *    this code should determine this value, using a suitable method.
144     *
145     * stackSize: in bytes, as set by the launcher, a zero-value indicates
146     *    this code determine this value, using a suitable method or omit the
147     *    line entirely.
148     */
149    static void showSettings(boolean printToStderr, String optionFlag,
150            long initialHeapSize, long maxHeapSize, long stackSize) {
151
152        initOutput(printToStderr);
153        String opts[] = optionFlag.split(":");
154        String optStr = (opts.length > 1 && opts[1] != null)
155                ? opts[1].trim()
156                : "all";
157        switch (optStr) {
158            case "vm":
159                printVmSettings(initialHeapSize, maxHeapSize, stackSize);
160                break;
161            case "properties":
162                printProperties();
163                break;
164            case "locale":
165                printLocale();
166                break;
167            default:
168                printVmSettings(initialHeapSize, maxHeapSize, stackSize);
169                printProperties();
170                printLocale();
171                break;
172        }
173    }
174
175    /*
176     * prints the main vm settings subopt/section
177     */
178    private static void printVmSettings(
179            long initialHeapSize, long maxHeapSize,
180            long stackSize) {
181
182        ostream.println(VM_SETTINGS);
183        if (stackSize != 0L) {
184            ostream.println(INDENT + "Stack Size: " +
185                    SizePrefix.scaleValue(stackSize));
186        }
187        if (initialHeapSize != 0L) {
188             ostream.println(INDENT + "Min. Heap Size: " +
189                    SizePrefix.scaleValue(initialHeapSize));
190        }
191        if (maxHeapSize != 0L) {
192            ostream.println(INDENT + "Max. Heap Size: " +
193                    SizePrefix.scaleValue(maxHeapSize));
194        } else {
195            ostream.println(INDENT + "Max. Heap Size (Estimated): "
196                    + SizePrefix.scaleValue(Runtime.getRuntime().maxMemory()));
197        }
198        ostream.println(INDENT + "Using VM: "
199                + System.getProperty("java.vm.name"));
200        ostream.println();
201    }
202
203    /*
204     * prints the properties subopt/section
205     */
206    private static void printProperties() {
207        Properties p = System.getProperties();
208        ostream.println(PROP_SETTINGS);
209        List<String> sortedPropertyKeys = new ArrayList<>();
210        sortedPropertyKeys.addAll(p.stringPropertyNames());
211        Collections.sort(sortedPropertyKeys);
212        for (String x : sortedPropertyKeys) {
213            printPropertyValue(x, p.getProperty(x));
214        }
215        ostream.println();
216    }
217
218    private static boolean isPath(String key) {
219        return key.endsWith(".dirs") || key.endsWith(".path");
220    }
221
222    private static void printPropertyValue(String key, String value) {
223        ostream.print(INDENT + key + " = ");
224        if (key.equals("line.separator")) {
225            for (byte b : value.getBytes()) {
226                switch (b) {
227                    case 0xd:
228                        ostream.print("\\r ");
229                        break;
230                    case 0xa:
231                        ostream.print("\\n ");
232                        break;
233                    default:
234                        // print any bizzare line separators in hex, but really
235                        // shouldn't happen.
236                        ostream.printf("0x%02X", b & 0xff);
237                        break;
238                }
239            }
240            ostream.println();
241            return;
242        }
243        if (!isPath(key)) {
244            ostream.println(value);
245            return;
246        }
247        String[] values = value.split(System.getProperty("path.separator"));
248        boolean first = true;
249        for (String s : values) {
250            if (first) { // first line treated specially
251                ostream.println(s);
252                first = false;
253            } else { // following lines prefix with indents
254                ostream.println(INDENT + INDENT + s);
255            }
256        }
257    }
258
259    /*
260     * prints the locale subopt/section
261     */
262    private static void printLocale() {
263        Locale locale = Locale.getDefault();
264        ostream.println(LOCALE_SETTINGS);
265        ostream.println(INDENT + "default locale = " +
266                locale.getDisplayLanguage());
267        ostream.println(INDENT + "default display locale = " +
268                Locale.getDefault(Category.DISPLAY).getDisplayName());
269        ostream.println(INDENT + "default format locale = " +
270                Locale.getDefault(Category.FORMAT).getDisplayName());
271        printLocales();
272        ostream.println();
273    }
274
275    private static void printLocales() {
276        Locale[] tlocales = Locale.getAvailableLocales();
277        final int len = tlocales == null ? 0 : tlocales.length;
278        if (len < 1 ) {
279            return;
280        }
281        // Locale does not implement Comparable so we convert it to String
282        // and sort it for pretty printing.
283        Set<String> sortedSet = new TreeSet<>();
284        for (Locale l : tlocales) {
285            sortedSet.add(l.toString());
286        }
287
288        ostream.print(INDENT + "available locales = ");
289        Iterator<String> iter = sortedSet.iterator();
290        final int last = len - 1;
291        for (int i = 0 ; iter.hasNext() ; i++) {
292            String s = iter.next();
293            ostream.print(s);
294            if (i != last) {
295                ostream.print(", ");
296            }
297            // print columns of 8
298            if ((i + 1) % 8 == 0) {
299                ostream.println();
300                ostream.print(INDENT + INDENT);
301            }
302        }
303    }
304
305    private enum SizePrefix {
306
307        KILO(1024, "K"),
308        MEGA(1024 * 1024, "M"),
309        GIGA(1024 * 1024 * 1024, "G"),
310        TERA(1024L * 1024L * 1024L * 1024L, "T");
311        long size;
312        String abbrev;
313
314        SizePrefix(long size, String abbrev) {
315            this.size = size;
316            this.abbrev = abbrev;
317        }
318
319        private static String scale(long v, SizePrefix prefix) {
320            return BigDecimal.valueOf(v).divide(BigDecimal.valueOf(prefix.size),
321                    2, RoundingMode.HALF_EVEN).toPlainString() + prefix.abbrev;
322        }
323        /*
324         * scale the incoming values to a human readable form, represented as
325         * K, M, G and T, see java.c parse_size for the scaled values and
326         * suffixes. The lowest possible scaled value is Kilo.
327         */
328        static String scaleValue(long v) {
329            if (v < MEGA.size) {
330                return scale(v, KILO);
331            } else if (v < GIGA.size) {
332                return scale(v, MEGA);
333            } else if (v < TERA.size) {
334                return scale(v, GIGA);
335            } else {
336                return scale(v, TERA);
337            }
338        }
339    }
340
341    /**
342     * A private helper method to get a localized message and also
343     * apply any arguments that we might pass.
344     */
345    private static String getLocalizedMessage(String key, Object... args) {
346        String msg = ResourceBundleHolder.RB.getString(key);
347        return (args != null) ? MessageFormat.format(msg, args) : msg;
348    }
349
350    /**
351     * The java -help message is split into 3 parts, an invariant, followed
352     * by a set of platform dependent variant messages, finally an invariant
353     * set of lines.
354     * This method initializes the help message for the first time, and also
355     * assembles the invariant header part of the message.
356     */
357    static void initHelpMessage(String progname) {
358        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.header",
359                (progname == null) ? "java" : progname ));
360        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.datamodel",
361                32));
362        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.datamodel",
363                64));
364    }
365
366    /**
367     * Appends the vm selection messages to the header, already created.
368     * initHelpSystem must already be called.
369     */
370    static void appendVmSelectMessage(String vm1, String vm2) {
371        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.vmselect",
372                vm1, vm2));
373    }
374
375    /**
376     * Appends the vm synoym message to the header, already created.
377     * initHelpSystem must be called before using this method.
378     */
379    static void appendVmSynonymMessage(String vm1, String vm2) {
380        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.hotspot",
381                vm1, vm2));
382    }
383
384    /**
385     * Appends the last invariant part to the previously created messages,
386     * and finishes up the printing to the desired output stream.
387     * initHelpSystem must be called before using this method.
388     */
389    static void printHelpMessage(boolean printToStderr) {
390        initOutput(printToStderr);
391        outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.footer",
392                File.pathSeparator));
393        ostream.println(outBuf.toString());
394    }
395
396    /**
397     * Prints the Xusage text to the desired output stream.
398     */
399    static void printXUsageMessage(boolean printToStderr) {
400        initOutput(printToStderr);
401        ostream.println(getLocalizedMessage("java.launcher.X.usage",
402                File.pathSeparator));
403        if (System.getProperty("os.name").contains("OS X")) {
404            ostream.println(getLocalizedMessage("java.launcher.X.macosx.usage",
405                        File.pathSeparator));
406        }
407    }
408
409    static void initOutput(boolean printToStderr) {
410        ostream =  (printToStderr) ? System.err : System.out;
411    }
412
413    static String getMainClassFromJar(String jarname) {
414        String mainValue = null;
415        try (JarFile jarFile = new JarFile(jarname)) {
416            Manifest manifest = jarFile.getManifest();
417            if (manifest == null) {
418                abort(null, "java.launcher.jar.error2", jarname);
419            }
420            Attributes mainAttrs = manifest.getMainAttributes();
421            if (mainAttrs == null) {
422                abort(null, "java.launcher.jar.error3", jarname);
423            }
424
425            // Main-Class
426            mainValue = mainAttrs.getValue(MAIN_CLASS);
427            if (mainValue == null) {
428                abort(null, "java.launcher.jar.error3", jarname);
429            }
430
431            // Add-Exports and Add-Opens to break encapsulation
432            String exports = mainAttrs.getValue(ADD_EXPORTS);
433            if (exports != null) {
434                addExportsOrOpens(exports, false);
435            }
436            String opens = mainAttrs.getValue(ADD_OPENS);
437            if (opens != null) {
438                addExportsOrOpens(opens, true);
439            }
440
441            /*
442             * Hand off to FXHelper if it detects a JavaFX application
443             * This must be done after ensuring a Main-Class entry
444             * exists to enforce compliance with the jar specification
445             */
446            if (mainAttrs.containsKey(
447                    new Attributes.Name(JAVAFX_APPLICATION_MARKER))) {
448                FXHelper.setFXLaunchParameters(jarname, LM_JAR);
449                return FXHelper.class.getName();
450            }
451
452            return mainValue.trim();
453        } catch (IOException ioe) {
454            abort(ioe, "java.launcher.jar.error1", jarname);
455        }
456        return null;
457    }
458
459    /**
460     * Process the Add-Exports or Add-Opens value. The value is
461     * {@code <module>/<package> ( <module>/<package>)*}.
462     */
463    static void addExportsOrOpens(String value, boolean open) {
464        for (String moduleAndPackage : value.split(" ")) {
465            String[] s = moduleAndPackage.trim().split("/");
466            if (s.length == 2) {
467                String mn = s[0];
468                String pn = s[1];
469                Layer.boot().findModule(mn).ifPresent(m -> {
470                    if (m.getDescriptor().packages().contains(pn)) {
471                        if (open) {
472                            Modules.addOpensToAllUnnamed(m, pn);
473                        } else {
474                            Modules.addExportsToAllUnnamed(m, pn);
475                        }
476                    }
477                });
478            }
479        }
480    }
481
482    // From src/share/bin/java.c:
483    //   enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR, LM_MODULE }
484
485    private static final int LM_UNKNOWN = 0;
486    private static final int LM_CLASS   = 1;
487    private static final int LM_JAR     = 2;
488    private static final int LM_MODULE  = 3;
489
490    static void abort(Throwable t, String msgKey, Object... args) {
491        if (msgKey != null) {
492            ostream.println(getLocalizedMessage(msgKey, args));
493        }
494        if (trace) {
495            if (t != null) {
496                t.printStackTrace();
497            } else {
498                Thread.dumpStack();
499            }
500        }
501        System.exit(1);
502    }
503
504    /**
505     * This method:
506     * 1. Loads the main class from the module or class path
507     * 2. Checks the public static void main method.
508     *
509     * @param printToStderr if set, all output will be routed to stderr
510     * @param mode LaunchMode as determined by the arguments passed on the
511     *             command line
512     * @param what the module name[/class], JAR file, or the main class
513     *             depending on the mode
514     *
515     * @return the application's main class
516     */
517    public static Class<?> checkAndLoadMain(boolean printToStderr,
518                                            int mode,
519                                            String what) {
520        initOutput(printToStderr);
521
522        Class<?> mainClass = (mode == LM_MODULE) ? loadModuleMainClass(what)
523                                                 : loadMainClass(mode, what);
524
525        validateMainClass(mainClass);
526
527        // record main class if not already set
528        if (appClass == null)
529            appClass = mainClass;
530
531        return mainClass;
532    }
533
534    /**
535     * Returns the main class for a module. The query is either a module name
536     * or module-name/main-class. For the former then the module's main class
537     * is obtained from the module descriptor (MainClass attribute).
538     */
539    private static Class<?> loadModuleMainClass(String what) {
540        int i = what.indexOf('/');
541        String mainModule;
542        String mainClass;
543        if (i == -1) {
544            mainModule = what;
545            mainClass = null;
546        } else {
547            mainModule = what.substring(0, i);
548            mainClass = what.substring(i+1);
549        }
550
551        // main module is in the boot layer
552        Layer layer = Layer.boot();
553        Optional<Module> om = layer.findModule(mainModule);
554        if (!om.isPresent()) {
555            // should not happen
556            throw new InternalError("Module " + mainModule + " not in boot Layer");
557        }
558        Module m = om.get();
559
560        // get main class
561        if (mainClass == null) {
562            Optional<String> omc = m.getDescriptor().mainClass();
563            if (!omc.isPresent()) {
564                abort(null, "java.launcher.module.error1", mainModule);
565            }
566            mainClass = omc.get();
567        }
568
569        // load the class from the module
570        Class<?> c = Class.forName(m, mainClass);
571        if (c == null &&  System.getProperty("os.name", "").contains("OS X")
572                && Normalizer.isNormalized(mainClass, Normalizer.Form.NFD)) {
573
574            String cn = Normalizer.normalize(mainClass, Normalizer.Form.NFC);
575            c = Class.forName(m, cn);
576
577        }
578        if (c == null) {
579            abort(null, "java.launcher.module.error2", mainClass, mainModule);
580        }
581
582        System.setProperty("jdk.module.main.class", c.getName());
583        return c;
584    }
585
586    /**
587     * Loads the main class from the class path (LM_CLASS or LM_JAR).
588     * If the main class extends FX Application then call on FXHelper to
589     * determine the main class to launch.
590     */
591    private static Class<?> loadMainClass(int mode, String what) {
592        // get the class name
593        String cn;
594        switch (mode) {
595            case LM_CLASS:
596                cn = what;
597                break;
598            case LM_JAR:
599                cn = getMainClassFromJar(what);
600                break;
601            default:
602                // should never happen
603                throw new InternalError("" + mode + ": Unknown launch mode");
604        }
605
606        // load the main class
607        cn = cn.replace('/', '.');
608        Class<?> mainClass = null;
609        ClassLoader scl = ClassLoader.getSystemClassLoader();
610        try {
611            mainClass = Class.forName(cn, false, scl);
612        } catch (NoClassDefFoundError | ClassNotFoundException cnfe) {
613            if (System.getProperty("os.name", "").contains("OS X")
614                    && Normalizer.isNormalized(cn, Normalizer.Form.NFD)) {
615                try {
616                    // On Mac OS X since all names with diacretic symbols are
617                    // given as decomposed it is possible that main class name
618                    // comes incorrectly from the command line and we have
619                    // to re-compose it
620                    String ncn = Normalizer.normalize(cn, Normalizer.Form.NFC);
621                    mainClass = Class.forName(ncn, false, scl);
622                } catch (NoClassDefFoundError | ClassNotFoundException cnfe1) {
623                    abort(cnfe, "java.launcher.cls.error1", cn);
624                }
625            } else {
626                abort(cnfe, "java.launcher.cls.error1", cn);
627            }
628        }
629
630        // record the main class
631        appClass = mainClass;
632
633        /*
634         * Check if FXHelper can launch it using the FX launcher. In an FX app,
635         * the main class may or may not have a main method, so do this before
636         * validating the main class.
637         */
638        if (JAVAFX_FXHELPER_CLASS_NAME_SUFFIX.equals(mainClass.getName()) ||
639            doesExtendFXApplication(mainClass)) {
640            // Will abort() if there are problems with FX runtime
641            FXHelper.setFXLaunchParameters(what, mode);
642            return FXHelper.class;
643        }
644        return mainClass;
645    }
646
647    /*
648     * Accessor method called by the launcher after getting the main class via
649     * checkAndLoadMain(). The "application class" is the class that is finally
650     * executed to start the application and in this case is used to report
651     * the correct application name, typically for UI purposes.
652     */
653    public static Class<?> getApplicationClass() {
654        return appClass;
655    }
656
657    /*
658     * Check if the given class is a JavaFX Application class. This is done
659     * in a way that does not cause the Application class to load or throw
660     * ClassNotFoundException if the JavaFX runtime is not available.
661     */
662    private static boolean doesExtendFXApplication(Class<?> mainClass) {
663        for (Class<?> sc = mainClass.getSuperclass(); sc != null;
664                sc = sc.getSuperclass()) {
665            if (sc.getName().equals(JAVAFX_APPLICATION_CLASS_NAME)) {
666                return true;
667            }
668        }
669        return false;
670    }
671
672    // Check the existence and signature of main and abort if incorrect
673    static void validateMainClass(Class<?> mainClass) {
674        Method mainMethod;
675        try {
676            mainMethod = mainClass.getMethod("main", String[].class);
677        } catch (NoSuchMethodException nsme) {
678            // invalid main or not FX application, abort with an error
679            abort(null, "java.launcher.cls.error4", mainClass.getName(),
680                  JAVAFX_APPLICATION_CLASS_NAME);
681            return; // Avoid compiler issues
682        }
683
684        /*
685         * getMethod (above) will choose the correct method, based
686         * on its name and parameter type, however, we still have to
687         * ensure that the method is static and returns a void.
688         */
689        int mod = mainMethod.getModifiers();
690        if (!Modifier.isStatic(mod)) {
691            abort(null, "java.launcher.cls.error2", "static",
692                  mainMethod.getDeclaringClass().getName());
693        }
694        if (mainMethod.getReturnType() != java.lang.Void.TYPE) {
695            abort(null, "java.launcher.cls.error3",
696                  mainMethod.getDeclaringClass().getName());
697        }
698    }
699
700    private static final String encprop = "sun.jnu.encoding";
701    private static String encoding = null;
702    private static boolean isCharsetSupported = false;
703
704    /*
705     * converts a c or a byte array to a platform specific string,
706     * previously implemented as a native method in the launcher.
707     */
708    static String makePlatformString(boolean printToStderr, byte[] inArray) {
709        initOutput(printToStderr);
710        if (encoding == null) {
711            encoding = System.getProperty(encprop);
712            isCharsetSupported = Charset.isSupported(encoding);
713        }
714        try {
715            String out = isCharsetSupported
716                    ? new String(inArray, encoding)
717                    : new String(inArray);
718            return out;
719        } catch (UnsupportedEncodingException uee) {
720            abort(uee, null);
721        }
722        return null; // keep the compiler happy
723    }
724
725    static String[] expandArgs(String[] argArray) {
726        List<StdArg> aList = new ArrayList<>();
727        for (String x : argArray) {
728            aList.add(new StdArg(x));
729        }
730        return expandArgs(aList);
731    }
732
733    static String[] expandArgs(List<StdArg> argList) {
734        ArrayList<String> out = new ArrayList<>();
735        if (trace) {
736            System.err.println("Incoming arguments:");
737        }
738        for (StdArg a : argList) {
739            if (trace) {
740                System.err.println(a);
741            }
742            if (a.needsExpansion) {
743                File x = new File(a.arg);
744                File parent = x.getParentFile();
745                String glob = x.getName();
746                if (parent == null) {
747                    parent = new File(".");
748                }
749                try (DirectoryStream<Path> dstream =
750                        Files.newDirectoryStream(parent.toPath(), glob)) {
751                    int entries = 0;
752                    for (Path p : dstream) {
753                        out.add(p.normalize().toString());
754                        entries++;
755                    }
756                    if (entries == 0) {
757                        out.add(a.arg);
758                    }
759                } catch (Exception e) {
760                    out.add(a.arg);
761                    if (trace) {
762                        System.err.println("Warning: passing argument as-is " + a);
763                        System.err.print(e);
764                    }
765                }
766            } else {
767                out.add(a.arg);
768            }
769        }
770        String[] oarray = new String[out.size()];
771        out.toArray(oarray);
772
773        if (trace) {
774            System.err.println("Expanded arguments:");
775            for (String x : oarray) {
776                System.err.println(x);
777            }
778        }
779        return oarray;
780    }
781
782    /* duplicate of the native StdArg struct */
783    private static class StdArg {
784        final String arg;
785        final boolean needsExpansion;
786        StdArg(String arg, boolean expand) {
787            this.arg = arg;
788            this.needsExpansion = expand;
789        }
790        // protocol: first char indicates whether expansion is required
791        // 'T' = true ; needs expansion
792        // 'F' = false; needs no expansion
793        StdArg(String in) {
794            this.arg = in.substring(1);
795            needsExpansion = in.charAt(0) == 'T';
796        }
797        public String toString() {
798            return "StdArg{" + "arg=" + arg + ", needsExpansion=" + needsExpansion + '}';
799        }
800    }
801
802    static final class FXHelper {
803
804        private static final String JAVAFX_GRAPHICS_MODULE_NAME =
805                "javafx.graphics";
806
807        private static final String JAVAFX_LAUNCHER_CLASS_NAME =
808                "com.sun.javafx.application.LauncherImpl";
809
810        /*
811         * The launch method used to invoke the JavaFX launcher. These must
812         * match the strings used in the launchApplication method.
813         *
814         * Command line                 JavaFX-App-Class  Launch mode  FX Launch mode
815         * java -cp fxapp.jar FXClass   N/A               LM_CLASS     "LM_CLASS"
816         * java -cp somedir FXClass     N/A               LM_CLASS     "LM_CLASS"
817         * java -jar fxapp.jar          Present           LM_JAR       "LM_JAR"
818         * java -jar fxapp.jar          Not Present       LM_JAR       "LM_JAR"
819         */
820        private static final String JAVAFX_LAUNCH_MODE_CLASS = "LM_CLASS";
821        private static final String JAVAFX_LAUNCH_MODE_JAR = "LM_JAR";
822
823        /*
824         * FX application launcher and launch method, so we can launch
825         * applications with no main method.
826         */
827        private static String fxLaunchName = null;
828        private static String fxLaunchMode = null;
829
830        private static Class<?> fxLauncherClass    = null;
831        private static Method   fxLauncherMethod   = null;
832
833        /*
834         * Set the launch params according to what was passed to LauncherHelper
835         * so we can use the same launch mode for FX. Abort if there is any
836         * issue with loading the FX runtime or with the launcher method.
837         */
838        private static void setFXLaunchParameters(String what, int mode) {
839
840            // find the module with the FX launcher
841            Optional<Module> om = Layer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);
842            if (!om.isPresent()) {
843                abort(null, "java.launcher.cls.error5");
844            }
845
846            // load the FX launcher class
847            fxLauncherClass = Class.forName(om.get(), JAVAFX_LAUNCHER_CLASS_NAME);
848            if (fxLauncherClass == null) {
849                abort(null, "java.launcher.cls.error5");
850            }
851
852            try {
853                /*
854                 * signature must be:
855                 * public static void launchApplication(String launchName,
856                 *     String launchMode, String[] args);
857                 */
858                fxLauncherMethod = fxLauncherClass.getMethod("launchApplication",
859                        String.class, String.class, String[].class);
860
861                // verify launcher signature as we do when validating the main method
862                int mod = fxLauncherMethod.getModifiers();
863                if (!Modifier.isStatic(mod)) {
864                    abort(null, "java.launcher.javafx.error1");
865                }
866                if (fxLauncherMethod.getReturnType() != java.lang.Void.TYPE) {
867                    abort(null, "java.launcher.javafx.error1");
868                }
869            } catch (NoSuchMethodException ex) {
870                abort(ex, "java.launcher.cls.error5", ex);
871            }
872
873            fxLaunchName = what;
874            switch (mode) {
875                case LM_CLASS:
876                    fxLaunchMode = JAVAFX_LAUNCH_MODE_CLASS;
877                    break;
878                case LM_JAR:
879                    fxLaunchMode = JAVAFX_LAUNCH_MODE_JAR;
880                    break;
881                default:
882                    // should not have gotten this far...
883                    throw new InternalError(mode + ": Unknown launch mode");
884            }
885        }
886
887        public static void main(String... args) throws Exception {
888            if (fxLauncherMethod == null
889                    || fxLaunchMode == null
890                    || fxLaunchName == null) {
891                throw new RuntimeException("Invalid JavaFX launch parameters");
892            }
893            // launch appClass via fxLauncherMethod
894            fxLauncherMethod.invoke(null,
895                    new Object[] {fxLaunchName, fxLaunchMode, args});
896        }
897    }
898
899    private static void formatCommaList(PrintStream out,
900                                        String prefix,
901                                        Collection<?> list)
902    {
903        if (list.isEmpty())
904            return;
905        out.format("%s", prefix);
906        boolean first = true;
907        for (Object ob : list) {
908            if (first) {
909                out.format(" %s", ob);
910                first = false;
911            } else {
912                out.format(", %s", ob);
913            }
914        }
915        out.format("%n");
916    }
917
918    /**
919     * Called by the launcher to list the observable modules.
920     * If called without any sub-options then the output is a simple list of
921     * the modules. If called with sub-options then the sub-options are the
922     * names of the modules to list (-listmods:java.base,java.desktop for
923     * example).
924     */
925    static void listModules(boolean printToStderr, String optionFlag)
926        throws IOException, ClassNotFoundException
927    {
928        initOutput(printToStderr);
929
930        ModuleFinder finder = jdk.internal.module.ModuleBootstrap.finder();
931
932        int colon = optionFlag.indexOf('=');
933        if (colon == -1) {
934            finder.findAll().stream()
935                .sorted(Comparator.comparing(ModuleReference::descriptor))
936                .forEach(md -> {
937                    ostream.println(midAndLocation(md.descriptor(),
938                                                   md.location()));
939                });
940        } else {
941            String[] names = optionFlag.substring(colon+1).split(",");
942            for (String name: names) {
943                ModuleReference mref = finder.find(name).orElse(null);
944                if (mref == null) {
945                    System.err.format("%s not observable!%n", name);
946                    continue;
947                }
948
949                ModuleDescriptor md = mref.descriptor();
950                if (md.isOpen())
951                    ostream.print("open ");
952                if (md.isAutomatic())
953                    ostream.print("automatic ");
954                ostream.println("module " + midAndLocation(md, mref.location()));
955
956                // unqualified exports (sorted by package)
957                Set<Exports> exports = new TreeSet<>(Comparator.comparing(Exports::source));
958                md.exports().stream().filter(e -> !e.isQualified()).forEach(exports::add);
959                for (Exports e : exports) {
960                    String modsAndSource = Stream.concat(toStringStream(e.modifiers()),
961                            Stream.of(e.source()))
962                            .collect(Collectors.joining(" "));
963                    ostream.format("  exports %s%n", modsAndSource);
964                }
965
966                for (Requires d : md.requires()) {
967                    ostream.format("  requires %s%n", d);
968                }
969                for (String s : md.uses()) {
970                    ostream.format("  uses %s%n", s);
971                }
972
973                for (Provides ps : md.provides()) {
974                    ostream.format("  provides %s with %s%n", ps.service(),
975                            ps.providers().stream().collect(Collectors.joining(", ")));
976                }
977
978                // qualified exports
979                for (Exports e : md.exports()) {
980                    if (e.isQualified()) {
981                        String modsAndSource = Stream.concat(toStringStream(e.modifiers()),
982                                Stream.of(e.source()))
983                                .collect(Collectors.joining(" "));
984                        ostream.format("  exports %s", modsAndSource);
985                        formatCommaList(ostream, " to", e.targets());
986                    }
987                }
988
989                // open packages
990                for (Opens obj: md.opens()) {
991                    String modsAndSource = Stream.concat(toStringStream(obj.modifiers()),
992                            Stream.of(obj.source()))
993                            .collect(Collectors.joining(" "));
994                    ostream.format("  opens %s", modsAndSource);
995                    if (obj.isQualified())
996                        formatCommaList(ostream, " to", obj.targets());
997                    else
998                        ostream.println();
999                }
1000
1001                // non-exported/non-open packages
1002                Set<String> concealed = new TreeSet<>(md.packages());
1003                md.exports().stream().map(Exports::source).forEach(concealed::remove);
1004                md.opens().stream().map(Opens::source).forEach(concealed::remove);
1005                concealed.forEach(p -> ostream.format("  contains %s%n", p));
1006            }
1007        }
1008    }
1009
1010    static <T> String toString(Set<T> s) {
1011        return toStringStream(s).collect(Collectors.joining(" "));
1012    }
1013
1014    static <T> Stream<String> toStringStream(Set<T> s) {
1015        return s.stream().map(e -> e.toString().toLowerCase());
1016    }
1017
1018    static String midAndLocation(ModuleDescriptor md, Optional<URI> location ) {
1019        URI loc = location.orElse(null);
1020        if (loc == null || loc.getScheme().equalsIgnoreCase("jrt"))
1021            return md.toNameAndVersion();
1022        else
1023            return md.toNameAndVersion() + " (" + loc + ")";
1024    }
1025}
1026