1/*
2 * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.internal.module;
27
28import java.io.File;
29import java.io.PrintStream;
30import java.lang.module.Configuration;
31import java.lang.module.ModuleDescriptor;
32import java.lang.module.ModuleFinder;
33import java.lang.module.ModuleReference;
34import java.lang.module.ResolvedModule;
35import java.net.URI;
36import java.nio.file.Path;
37import java.nio.file.Paths;
38import java.util.ArrayList;
39import java.util.Collections;
40import java.util.HashMap;
41import java.util.HashSet;
42import java.util.Iterator;
43import java.util.List;
44import java.util.Map;
45import java.util.NoSuchElementException;
46import java.util.Optional;
47import java.util.Set;
48import java.util.function.Function;
49
50import jdk.internal.loader.BootLoader;
51import jdk.internal.loader.BuiltinClassLoader;
52import jdk.internal.misc.JavaLangAccess;
53import jdk.internal.misc.SharedSecrets;
54import jdk.internal.perf.PerfCounter;
55
56/**
57 * Initializes/boots the module system.
58 *
59 * The {@link #boot() boot} method is called early in the startup to initialize
60 * the module system. In summary, the boot method creates a Configuration by
61 * resolving a set of module names specified via the launcher (or equivalent)
62 * -m and --add-modules options. The modules are located on a module path that
63 * is constructed from the upgrade module path, system modules, and application
64 * module path. The Configuration is instantiated as the boot layer with each
65 * module in the the configuration defined to a class loader.
66 */
67
68public final class ModuleBootstrap {
69    private ModuleBootstrap() { }
70
71    private static final String JAVA_BASE = "java.base";
72
73    private static final String JAVA_SE = "java.se";
74
75    // the token for "all default modules"
76    private static final String ALL_DEFAULT = "ALL-DEFAULT";
77
78    // the token for "all unnamed modules"
79    private static final String ALL_UNNAMED = "ALL-UNNAMED";
80
81    // the token for "all system modules"
82    private static final String ALL_SYSTEM = "ALL-SYSTEM";
83
84    // the token for "all modules on the module path"
85    private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
86
87    // The ModulePatcher for the initial configuration
88    private static final ModulePatcher patcher = initModulePatcher();
89
90    // ModuleFinders for the initial configuration
91    private static ModuleFinder unlimitedFinder;
92    private static ModuleFinder limitedFinder;
93
94    /**
95     * Returns the ModulePatcher for the initial configuration.
96     */
97    public static ModulePatcher patcher() {
98        return patcher;
99    }
100
101    /**
102     * Returns the ModuleFinder for the initial configuration before observability
103     * is limited by the --limit-modules command line option.
104     */
105    public static ModuleFinder unlimitedFinder() {
106        assert unlimitedFinder != null;
107        return unlimitedFinder;
108    }
109
110    /**
111     * Returns the ModuleFinder for the initial configuration.
112     */
113    public static ModuleFinder limitedFinder() {
114        assert limitedFinder != null;
115        return limitedFinder;
116    }
117
118    /**
119     * Initialize the module system, returning the boot layer.
120     *
121     * @see java.lang.System#initPhase2()
122     */
123    public static ModuleLayer boot() {
124
125        // Step 1: Locate system modules (may be patched)
126
127        long t1 = System.nanoTime();
128        ModuleFinder systemModules = ModuleFinder.ofSystem();
129        PerfCounters.systemModulesTime.addElapsedTimeFrom(t1);
130
131
132        // Step 2: Define and load java.base. This patches all classes loaded
133        // to date so that they are members of java.base. Once java.base is
134        // loaded then resources in java.base are available for error messages
135        // needed from here on.
136
137        long t2 = System.nanoTime();
138
139        ModuleReference base = systemModules.find(JAVA_BASE).orElse(null);
140        if (base == null)
141            throw new InternalError(JAVA_BASE + " not found");
142        URI baseUri = base.location().orElse(null);
143        if (baseUri == null)
144            throw new InternalError(JAVA_BASE + " does not have a location");
145        BootLoader.loadModule(base);
146        Modules.defineModule(null, base.descriptor(), baseUri);
147
148        PerfCounters.defineBaseTime.addElapsedTimeFrom(t2);
149
150
151        // Step 2a: If --validate-modules is specified then the VM needs to
152        // start with only java.base, all other options are ignored.
153
154        String propValue = getAndRemoveProperty("jdk.module.minimumBoot");
155        if (propValue != null) {
156            return createMinimalBootLayer();
157        }
158
159
160        // Step 3: Construct the module path and the set of root modules to
161        // resolve. If --limit-modules is specified then it limits the set
162        // modules that are observable.
163
164        long t3 = System.nanoTime();
165
166        // --upgrade-module-path option specified to launcher
167        ModuleFinder upgradeModulePath
168            = createModulePathFinder("jdk.module.upgrade.path");
169        if (upgradeModulePath != null)
170            systemModules = ModuleFinder.compose(upgradeModulePath, systemModules);
171
172        // --module-path option specified to the launcher
173        ModuleFinder appModulePath = createModulePathFinder("jdk.module.path");
174
175        // The module finder: [--upgrade-module-path] system [--module-path]
176        ModuleFinder finder = systemModules;
177        if (appModulePath != null)
178            finder = ModuleFinder.compose(finder, appModulePath);
179
180        // The root modules to resolve
181        Set<String> roots = new HashSet<>();
182
183        // launcher -m option to specify the main/initial module
184        String mainModule = System.getProperty("jdk.module.main");
185        if (mainModule != null)
186            roots.add(mainModule);
187
188        // additional module(s) specified by --add-modules
189        boolean addAllDefaultModules = false;
190        boolean addAllSystemModules = false;
191        boolean addAllApplicationModules = false;
192        for (String mod: getExtraAddModules()) {
193            switch (mod) {
194                case ALL_DEFAULT:
195                    addAllDefaultModules = true;
196                    break;
197                case ALL_SYSTEM:
198                    addAllSystemModules = true;
199                    break;
200                case ALL_MODULE_PATH:
201                    addAllApplicationModules = true;
202                    break;
203                default :
204                    roots.add(mod);
205            }
206        }
207
208        // --limit-modules
209        unlimitedFinder = finder;
210        propValue = getAndRemoveProperty("jdk.module.limitmods");
211        if (propValue != null) {
212            Set<String> mods = new HashSet<>();
213            for (String mod: propValue.split(",")) {
214                mods.add(mod);
215            }
216            finder = limitFinder(finder, mods, roots);
217        }
218        limitedFinder = finder;
219
220        // If there is no initial module specified then assume that the initial
221        // module is the unnamed module of the application class loader. This
222        // is implemented by resolving "java.se" and all (non-java.*) modules
223        // that export an API. If "java.se" is not observable then all java.*
224        // modules are resolved. Modules that have the DO_NOT_RESOLVE_BY_DEFAULT
225        // bit set in their ModuleResolution attribute flags are excluded from
226        // the default set of roots.
227        if (mainModule == null || addAllDefaultModules) {
228            boolean hasJava = false;
229            if (systemModules.find(JAVA_SE).isPresent()) {
230                // java.se is a system module
231                if (finder == systemModules || finder.find(JAVA_SE).isPresent()) {
232                    // java.se is observable
233                    hasJava = true;
234                    roots.add(JAVA_SE);
235                }
236            }
237
238            for (ModuleReference mref : systemModules.findAll()) {
239                String mn = mref.descriptor().name();
240                if (hasJava && mn.startsWith("java."))
241                    continue;
242
243                if (ModuleResolution.doNotResolveByDefault(mref))
244                    continue;
245
246                // add as root if observable and exports at least one package
247                if ((finder == systemModules || finder.find(mn).isPresent())) {
248                    ModuleDescriptor descriptor = mref.descriptor();
249                    for (ModuleDescriptor.Exports e : descriptor.exports()) {
250                        if (!e.isQualified()) {
251                            roots.add(mn);
252                            break;
253                        }
254                    }
255                }
256            }
257        }
258
259        // If `--add-modules ALL-SYSTEM` is specified then all observable system
260        // modules will be resolved.
261        if (addAllSystemModules) {
262            ModuleFinder f = finder;  // observable modules
263            systemModules.findAll()
264                .stream()
265                .map(ModuleReference::descriptor)
266                .map(ModuleDescriptor::name)
267                .filter(mn -> f.find(mn).isPresent())  // observable
268                .forEach(mn -> roots.add(mn));
269        }
270
271        // If `--add-modules ALL-MODULE-PATH` is specified then all observable
272        // modules on the application module path will be resolved.
273        if (appModulePath != null && addAllApplicationModules) {
274            ModuleFinder f = finder;  // observable modules
275            appModulePath.findAll()
276                .stream()
277                .map(ModuleReference::descriptor)
278                .map(ModuleDescriptor::name)
279                .filter(mn -> f.find(mn).isPresent())  // observable
280                .forEach(mn -> roots.add(mn));
281        }
282
283        PerfCounters.optionsAndRootsTime.addElapsedTimeFrom(t3);
284
285
286        // Step 4: Resolve the root modules, with service binding, to create
287        // the configuration for the boot layer.
288
289        long t4 = System.nanoTime();
290
291        // determine if post resolution checks are needed
292        boolean needPostResolutionChecks = true;
293        if (baseUri.getScheme().equals("jrt")   // toLowerCase not needed here
294                && (upgradeModulePath == null)
295                && (appModulePath == null)
296                && (patcher.isEmpty())) {
297            needPostResolutionChecks = false;
298        }
299
300        PrintStream traceOutput = null;
301        propValue = getAndRemoveProperty("jdk.module.showModuleResolution");
302        if (propValue != null && Boolean.parseBoolean(propValue))
303            traceOutput = System.out;
304
305        // run the resolver to create the configuration
306        Configuration cf = SharedSecrets.getJavaLangModuleAccess()
307                .resolveAndBind(finder,
308                                roots,
309                                needPostResolutionChecks,
310                                traceOutput);
311
312        PerfCounters.resolveTime.addElapsedTimeFrom(t4);
313
314
315        // Step 5: Map the modules in the configuration to class loaders.
316        // The static configuration provides the mapping of standard and JDK
317        // modules to the boot and platform loaders. All other modules (JDK
318        // tool modules, and both explicit and automatic modules on the
319        // application module path) are defined to the application class
320        // loader.
321
322        long t5 = System.nanoTime();
323
324        // mapping of modules to class loaders
325        Function<String, ClassLoader> clf = ModuleLoaderMap.mappingFunction(cf);
326
327        // check that all modules to be mapped to the boot loader will be
328        // loaded from the runtime image
329        if (needPostResolutionChecks) {
330            for (ResolvedModule resolvedModule : cf.modules()) {
331                ModuleReference mref = resolvedModule.reference();
332                String name = mref.descriptor().name();
333                ClassLoader cl = clf.apply(name);
334                if (cl == null) {
335                    if (upgradeModulePath != null
336                            && upgradeModulePath.find(name).isPresent())
337                        fail(name + ": cannot be loaded from upgrade module path");
338                    if (!systemModules.find(name).isPresent())
339                        fail(name + ": cannot be loaded from application module path");
340                }
341            }
342
343            // check if module specified in --patch-module is present
344            for (String mn: patcher.patchedModules()) {
345                if (!cf.findModule(mn).isPresent()) {
346                    warnUnknownModule(PATCH_MODULE, mn);
347                }
348            }
349        }
350
351        // check for split packages in the modules mapped to the built-in loaders
352        if (SystemModules.hasSplitPackages() || needPostResolutionChecks) {
353            checkSplitPackages(cf, clf);
354        }
355
356        // load/register the modules with the built-in class loaders
357        loadModules(cf, clf);
358
359        PerfCounters.loadModulesTime.addElapsedTimeFrom(t5);
360
361
362        // Step 6: Define all modules to the VM
363
364        long t6 = System.nanoTime();
365        ModuleLayer bootLayer = ModuleLayer.empty().defineModules(cf, clf);
366        PerfCounters.layerCreateTime.addElapsedTimeFrom(t6);
367
368
369        // Step 7: Miscellaneous
370
371        // check incubating status
372        checkIncubatingStatus(cf);
373
374        // --add-reads, --add-exports/--add-opens, and -illegal-access
375        long t7 = System.nanoTime();
376        addExtraReads(bootLayer);
377        boolean extraExportsOrOpens = addExtraExportsAndOpens(bootLayer);
378        addIllegalAccess(bootLayer, upgradeModulePath, extraExportsOrOpens);
379        PerfCounters.adjustModulesTime.addElapsedTimeFrom(t7);
380
381        // total time to initialize
382        PerfCounters.bootstrapTime.addElapsedTimeFrom(t1);
383
384        return bootLayer;
385    }
386
387    /**
388     * Create a "minimal" boot module layer that only contains java.base.
389     */
390    private static ModuleLayer createMinimalBootLayer() {
391        Configuration cf = SharedSecrets.getJavaLangModuleAccess()
392            .resolveAndBind(ModuleFinder.ofSystem(),
393                            Set.of(JAVA_BASE),
394                            false,
395                            null);
396
397        Function<String, ClassLoader> clf = ModuleLoaderMap.mappingFunction(cf);
398        return ModuleLayer.empty().defineModules(cf, clf);
399    }
400
401    /**
402     * Load/register the modules to the built-in class loaders.
403     */
404    private static void loadModules(Configuration cf,
405                                    Function<String, ClassLoader> clf) {
406        for (ResolvedModule resolvedModule : cf.modules()) {
407            ModuleReference mref = resolvedModule.reference();
408            String name = resolvedModule.name();
409            ClassLoader loader = clf.apply(name);
410            if (loader == null) {
411                // skip java.base as it is already loaded
412                if (!name.equals(JAVA_BASE)) {
413                    BootLoader.loadModule(mref);
414                }
415            } else if (loader instanceof BuiltinClassLoader) {
416                ((BuiltinClassLoader) loader).loadModule(mref);
417            }
418        }
419    }
420
421    /**
422     * Checks for split packages between modules defined to the built-in class
423     * loaders.
424     */
425    private static void checkSplitPackages(Configuration cf,
426                                           Function<String, ClassLoader> clf) {
427        Map<String, String> packageToModule = new HashMap<>();
428        for (ResolvedModule resolvedModule : cf.modules()) {
429            ModuleDescriptor descriptor = resolvedModule.reference().descriptor();
430            String name = descriptor.name();
431            ClassLoader loader = clf.apply(name);
432            if (loader == null || loader instanceof BuiltinClassLoader) {
433                for (String p : descriptor.packages()) {
434                    String other = packageToModule.putIfAbsent(p, name);
435                    if (other != null) {
436                        String msg = "Package " + p + " in both module "
437                                     + name + " and module " + other;
438                        throw new LayerInstantiationException(msg);
439                    }
440                }
441            }
442
443        }
444    }
445
446    /**
447     * Returns a ModuleFinder that limits observability to the given root
448     * modules, their transitive dependences, plus a set of other modules.
449     */
450    private static ModuleFinder limitFinder(ModuleFinder finder,
451                                            Set<String> roots,
452                                            Set<String> otherMods)
453    {
454        // resolve all root modules
455        Configuration cf = Configuration.empty().resolve(finder,
456                                                         ModuleFinder.of(),
457                                                         roots);
458
459        // module name -> reference
460        Map<String, ModuleReference> map = new HashMap<>();
461
462        // root modules and their transitive dependences
463        cf.modules().stream()
464            .map(ResolvedModule::reference)
465            .forEach(mref -> map.put(mref.descriptor().name(), mref));
466
467        // additional modules
468        otherMods.stream()
469            .map(finder::find)
470            .flatMap(Optional::stream)
471            .forEach(mref -> map.putIfAbsent(mref.descriptor().name(), mref));
472
473        // set of modules that are observable
474        Set<ModuleReference> mrefs = new HashSet<>(map.values());
475
476        return new ModuleFinder() {
477            @Override
478            public Optional<ModuleReference> find(String name) {
479                return Optional.ofNullable(map.get(name));
480            }
481            @Override
482            public Set<ModuleReference> findAll() {
483                return mrefs;
484            }
485        };
486    }
487
488    /**
489     * Creates a finder from the module path that is the value of the given
490     * system property and optionally patched by --patch-module
491     */
492    private static ModuleFinder createModulePathFinder(String prop) {
493        String s = System.getProperty(prop);
494        if (s == null) {
495            return null;
496        } else {
497            String[] dirs = s.split(File.pathSeparator);
498            Path[] paths = new Path[dirs.length];
499            int i = 0;
500            for (String dir: dirs) {
501                paths[i++] = Paths.get(dir);
502            }
503            return ModulePath.of(patcher, paths);
504        }
505    }
506
507    /**
508     * Initialize the module patcher for the initial configuration passed on the
509     * value of the --patch-module options.
510     */
511    private static ModulePatcher initModulePatcher() {
512        Map<String, List<String>> map = decode("jdk.module.patch.",
513                File.pathSeparator,
514                false);
515        return new ModulePatcher(map);
516    }
517
518    /**
519     * Returns the set of module names specified via --add-modules options
520     * on the command line
521     */
522    private static Set<String> getExtraAddModules() {
523        String prefix = "jdk.module.addmods.";
524        int index = 0;
525
526        // the system property is removed after decoding
527        String value = getAndRemoveProperty(prefix + index);
528        if (value == null) {
529            return Collections.emptySet();
530        }
531
532        Set<String> modules = new HashSet<>();
533        while (value != null) {
534            for (String s : value.split(",")) {
535                if (s.length() > 0) modules.add(s);
536            }
537            index++;
538            value = getAndRemoveProperty(prefix + index);
539        }
540
541        return modules;
542    }
543
544    /**
545     * Process the --add-reads options to add any additional read edges that
546     * are specified on the command-line.
547     */
548    private static void addExtraReads(ModuleLayer bootLayer) {
549
550        // decode the command line options
551        Map<String, List<String>> map = decode("jdk.module.addreads.");
552        if (map.isEmpty())
553            return;
554
555        for (Map.Entry<String, List<String>> e : map.entrySet()) {
556
557            // the key is $MODULE
558            String mn = e.getKey();
559            Optional<Module> om = bootLayer.findModule(mn);
560            if (!om.isPresent()) {
561                warnUnknownModule(ADD_READS, mn);
562                continue;
563            }
564            Module m = om.get();
565
566            // the value is the set of other modules (by name)
567            for (String name : e.getValue()) {
568                if (ALL_UNNAMED.equals(name)) {
569                    Modules.addReadsAllUnnamed(m);
570                } else {
571                    om = bootLayer.findModule(name);
572                    if (om.isPresent()) {
573                        Modules.addReads(m, om.get());
574                    } else {
575                        warnUnknownModule(ADD_READS, name);
576                    }
577                }
578            }
579        }
580    }
581
582    /**
583     * Process the --add-exports and --add-opens options to export/open
584     * additional packages specified on the command-line.
585     */
586    private static boolean addExtraExportsAndOpens(ModuleLayer bootLayer) {
587        boolean extraExportsOrOpens = false;
588
589        // --add-exports
590        String prefix = "jdk.module.addexports.";
591        Map<String, List<String>> extraExports = decode(prefix);
592        if (!extraExports.isEmpty()) {
593            addExtraExportsOrOpens(bootLayer, extraExports, false);
594            extraExportsOrOpens = true;
595        }
596
597
598        // --add-opens
599        prefix = "jdk.module.addopens.";
600        Map<String, List<String>> extraOpens = decode(prefix);
601        if (!extraOpens.isEmpty()) {
602            addExtraExportsOrOpens(bootLayer, extraOpens, true);
603            extraExportsOrOpens = true;
604        }
605
606        return extraExportsOrOpens;
607    }
608
609    private static void addExtraExportsOrOpens(ModuleLayer bootLayer,
610                                               Map<String, List<String>> map,
611                                               boolean opens)
612    {
613        String option = opens ? ADD_OPENS : ADD_EXPORTS;
614        for (Map.Entry<String, List<String>> e : map.entrySet()) {
615
616            // the key is $MODULE/$PACKAGE
617            String key = e.getKey();
618            String[] s = key.split("/");
619            if (s.length != 2)
620                fail(unableToParse(option, "<module>/<package>", key));
621
622            String mn = s[0];
623            String pn = s[1];
624            if (mn.isEmpty() || pn.isEmpty())
625                fail(unableToParse(option, "<module>/<package>", key));
626
627            // The exporting module is in the boot layer
628            Module m;
629            Optional<Module> om = bootLayer.findModule(mn);
630            if (!om.isPresent()) {
631                warnUnknownModule(option, mn);
632                continue;
633            }
634
635            m = om.get();
636
637            if (!m.getDescriptor().packages().contains(pn)) {
638                warn("package " + pn + " not in " + mn);
639                continue;
640            }
641
642            // the value is the set of modules to export to (by name)
643            for (String name : e.getValue()) {
644                boolean allUnnamed = false;
645                Module other = null;
646                if (ALL_UNNAMED.equals(name)) {
647                    allUnnamed = true;
648                } else {
649                    om = bootLayer.findModule(name);
650                    if (om.isPresent()) {
651                        other = om.get();
652                    } else {
653                        warnUnknownModule(option, name);
654                        continue;
655                    }
656                }
657                if (allUnnamed) {
658                    if (opens) {
659                        Modules.addOpensToAllUnnamed(m, pn);
660                    } else {
661                        Modules.addExportsToAllUnnamed(m, pn);
662                    }
663                } else {
664                    if (opens) {
665                        Modules.addOpens(m, pn, other);
666                    } else {
667                        Modules.addExports(m, pn, other);
668                    }
669                }
670
671            }
672        }
673    }
674
675    /**
676     * Process the --illegal-access option (and its default) to open packages
677     * of system modules in the boot layer to code in unnamed modules.
678     */
679    private static void addIllegalAccess(ModuleLayer bootLayer,
680                                         ModuleFinder upgradeModulePath,
681                                         boolean extraExportsOrOpens) {
682        String value = getAndRemoveProperty("jdk.module.illegalAccess");
683        IllegalAccessLogger.Mode mode = IllegalAccessLogger.Mode.ONESHOT;
684        if (value != null) {
685            switch (value) {
686                case "deny":
687                    return;
688                case "permit":
689                    break;
690                case "warn":
691                    mode = IllegalAccessLogger.Mode.WARN;
692                    break;
693                case "debug":
694                    mode = IllegalAccessLogger.Mode.DEBUG;
695                    break;
696                default:
697                    fail("Value specified to --illegal-access not recognized:"
698                            + " '" + value + "'");
699                    return;
700            }
701        }
702        IllegalAccessLogger.Builder builder
703            = new IllegalAccessLogger.Builder(mode, System.err);
704
705        Map<String, Set<String>> map1 = SystemModules.concealedPackagesToOpen();
706        Map<String, Set<String>> map2 = SystemModules.exportedPackagesToOpen();
707        if (map1.isEmpty() && map2.isEmpty()) {
708            // need to generate maps when on exploded build
709            IllegalAccessMaps maps = IllegalAccessMaps.generate(limitedFinder());
710            map1 = maps.concealedPackagesToOpen();
711            map2 = maps.exportedPackagesToOpen();
712        }
713
714        // open specific packages in the system modules
715        for (Module m : bootLayer.modules()) {
716            ModuleDescriptor descriptor = m.getDescriptor();
717            String name = m.getName();
718
719            // skip open modules
720            if (descriptor.isOpen()) {
721                continue;
722            }
723
724            // skip modules loaded from the upgrade module path
725            if (upgradeModulePath != null
726                && upgradeModulePath.find(name).isPresent()) {
727                continue;
728            }
729
730            Set<String> concealedPackages = map1.getOrDefault(name, Set.of());
731            Set<String> exportedPackages = map2.getOrDefault(name, Set.of());
732
733            // refresh the set of concealed and exported packages if needed
734            if (extraExportsOrOpens) {
735                concealedPackages = new HashSet<>(concealedPackages);
736                exportedPackages = new HashSet<>(exportedPackages);
737                Iterator<String> iterator = concealedPackages.iterator();
738                while (iterator.hasNext()) {
739                    String pn = iterator.next();
740                    if (m.isExported(pn, BootLoader.getUnnamedModule())) {
741                        // concealed package is exported to ALL-UNNAMED
742                        iterator.remove();
743                        exportedPackages.add(pn);
744                    }
745                }
746                iterator = exportedPackages.iterator();
747                while (iterator.hasNext()) {
748                    String pn = iterator.next();
749                    if (m.isOpen(pn, BootLoader.getUnnamedModule())) {
750                        // exported package is opened to ALL-UNNAMED
751                        iterator.remove();
752                    }
753                }
754            }
755
756            // log reflective access to all types in concealed packages
757            builder.logAccessToConcealedPackages(m, concealedPackages);
758
759            // log reflective access to non-public members/types in exported packages
760            builder.logAccessToExportedPackages(m, exportedPackages);
761
762            // open the packages to unnamed modules
763            JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
764            jla.addOpensToAllUnnamed(m, concat(concealedPackages.iterator(),
765                                               exportedPackages.iterator()));
766        }
767
768        builder.complete();
769    }
770
771    /**
772     * Decodes the values of --add-reads, -add-exports, --add-opens or
773     * --patch-modules options that are encoded in system properties.
774     *
775     * @param prefix the system property prefix
776     * @praam regex the regex for splitting the RHS of the option value
777     */
778    private static Map<String, List<String>> decode(String prefix,
779                                                    String regex,
780                                                    boolean allowDuplicates) {
781        int index = 0;
782        // the system property is removed after decoding
783        String value = getAndRemoveProperty(prefix + index);
784        if (value == null)
785            return Collections.emptyMap();
786
787        Map<String, List<String>> map = new HashMap<>();
788
789        while (value != null) {
790
791            int pos = value.indexOf('=');
792            if (pos == -1)
793                fail(unableToParse(option(prefix), "<module>=<value>", value));
794            if (pos == 0)
795                fail(unableToParse(option(prefix), "<module>=<value>", value));
796
797            // key is <module> or <module>/<package>
798            String key = value.substring(0, pos);
799
800            String rhs = value.substring(pos+1);
801            if (rhs.isEmpty())
802                fail(unableToParse(option(prefix), "<module>=<value>", value));
803
804            // value is <module>(,<module>)* or <file>(<pathsep><file>)*
805            if (!allowDuplicates && map.containsKey(key))
806                fail(key + " specified more than once to " + option(prefix));
807            List<String> values = map.computeIfAbsent(key, k -> new ArrayList<>());
808            int ntargets = 0;
809            for (String s : rhs.split(regex)) {
810                if (s.length() > 0) {
811                    values.add(s);
812                    ntargets++;
813                }
814            }
815            if (ntargets == 0)
816                fail("Target must be specified: " + option(prefix) + " " + value);
817
818            index++;
819            value = getAndRemoveProperty(prefix + index);
820        }
821
822        return map;
823    }
824
825    /**
826     * Decodes the values of --add-reads, -add-exports or --add-opens
827     * which use the "," to separate the RHS of the option value.
828     */
829    private static Map<String, List<String>> decode(String prefix) {
830        return decode(prefix, ",", true);
831    }
832
833    /**
834     * Gets and remove the named system property
835     */
836    private static String getAndRemoveProperty(String key) {
837        return (String)System.getProperties().remove(key);
838    }
839
840    /**
841     * Checks incubating status of modules in the configuration
842     */
843    private static void checkIncubatingStatus(Configuration cf) {
844        String incubating = null;
845        for (ResolvedModule resolvedModule : cf.modules()) {
846            ModuleReference mref = resolvedModule.reference();
847
848            // emit warning if the WARN_INCUBATING module resolution bit set
849            if (ModuleResolution.hasIncubatingWarning(mref)) {
850                String mn = mref.descriptor().name();
851                if (incubating == null) {
852                    incubating = mn;
853                } else {
854                    incubating += ", " + mn;
855                }
856            }
857        }
858        if (incubating != null)
859            warn("Using incubator modules: " + incubating);
860    }
861
862    /**
863     * Throws a RuntimeException with the given message
864     */
865    static void fail(String m) {
866        throw new RuntimeException(m);
867    }
868
869    static void warn(String m) {
870        System.err.println("WARNING: " + m);
871    }
872
873    static void warnUnknownModule(String option, String mn) {
874        warn("Unknown module: " + mn + " specified to " + option);
875    }
876
877    static String unableToParse(String option, String text, String value) {
878        return "Unable to parse " +  option + " " + text + ": " + value;
879    }
880
881    private static final String ADD_MODULES  = "--add-modules";
882    private static final String ADD_EXPORTS  = "--add-exports";
883    private static final String ADD_OPENS    = "--add-opens";
884    private static final String ADD_READS    = "--add-reads";
885    private static final String PATCH_MODULE = "--patch-module";
886
887
888    /*
889     * Returns the command-line option name corresponds to the specified
890     * system property prefix.
891     */
892    static String option(String prefix) {
893        switch (prefix) {
894            case "jdk.module.addexports.":
895                return ADD_EXPORTS;
896            case "jdk.module.addopens.":
897                return ADD_OPENS;
898            case "jdk.module.addreads.":
899                return ADD_READS;
900            case "jdk.module.patch.":
901                return PATCH_MODULE;
902            case "jdk.module.addmods.":
903                return ADD_MODULES;
904            default:
905                throw new IllegalArgumentException(prefix);
906        }
907    }
908
909    static <T> Iterator<T> concat(Iterator<T> iterator1, Iterator<T> iterator2) {
910        return new Iterator<T>() {
911            @Override
912            public boolean hasNext() {
913                return iterator1.hasNext() || iterator2.hasNext();
914            }
915            @Override
916            public T next() {
917                if (iterator1.hasNext()) return iterator1.next();
918                if (iterator2.hasNext()) return iterator2.next();
919                throw new NoSuchElementException();
920            }
921        };
922    }
923
924    static class PerfCounters {
925
926        static PerfCounter systemModulesTime
927            = PerfCounter.newPerfCounter("jdk.module.bootstrap.systemModulesTime");
928        static PerfCounter defineBaseTime
929            = PerfCounter.newPerfCounter("jdk.module.bootstrap.defineBaseTime");
930        static PerfCounter optionsAndRootsTime
931            = PerfCounter.newPerfCounter("jdk.module.bootstrap.optionsAndRootsTime");
932        static PerfCounter resolveTime
933            = PerfCounter.newPerfCounter("jdk.module.bootstrap.resolveTime");
934        static PerfCounter layerCreateTime
935            = PerfCounter.newPerfCounter("jdk.module.bootstrap.layerCreateTime");
936        static PerfCounter loadModulesTime
937            = PerfCounter.newPerfCounter("jdk.module.bootstrap.loadModulesTime");
938        static PerfCounter adjustModulesTime
939            = PerfCounter.newPerfCounter("jdk.module.bootstrap.adjustModulesTime");
940        static PerfCounter bootstrapTime
941            = PerfCounter.newPerfCounter("jdk.module.bootstrap.totalTime");
942    }
943}
944