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