1 /*
   2  * Copyright (c) 2014, 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 
  26 package jdk.internal.module;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.io.PrintStream;
  31 import java.io.UncheckedIOException;
  32 import java.lang.module.Configuration;
  33 import java.lang.module.ModuleDescriptor;
  34 import java.lang.module.ModuleFinder;
  35 import java.lang.module.ModuleReference;
  36 import java.lang.module.ResolvedModule;
  37 import java.lang.reflect.Layer;
  38 import java.lang.reflect.Module;
  39 import java.net.URI;
  40 import java.nio.file.Files;
  41 import java.nio.file.Path;
  42 import java.nio.file.Paths;
  43 import java.util.ArrayList;
  44 import java.util.Collections;
  45 import java.util.HashMap;
  46 import java.util.HashSet;
  47 import java.util.List;
  48 import java.util.Map;
  49 import java.util.Optional;
  50 import java.util.Set;
  51 import java.util.function.Function;
  52 import java.util.stream.Stream;
  53 
  54 import jdk.internal.loader.BootLoader;
  55 import jdk.internal.loader.BuiltinClassLoader;
  56 import jdk.internal.misc.SharedSecrets;
  57 import jdk.internal.perf.PerfCounter;
  58 
  59 /**
  60  * Initializes/boots the module system.
  61  *
  62  * The {@link #boot() boot} method is called early in the startup to initialize
  63  * the module system. In summary, the boot method creates a Configuration by
  64  * resolving a set of module names specified via the launcher (or equivalent)
  65  * -m and --add-modules options. The modules are located on a module path that
  66  * is constructed from the upgrade module path, system modules, and application
  67  * module path. The Configuration is instantiated as the boot Layer with each
  68  * module in the the configuration defined to one of the built-in class loaders.
  69  */
  70 
  71 public final class ModuleBootstrap {
  72     private ModuleBootstrap() { }
  73 
  74     private static final String JAVA_BASE = "java.base";
  75 
  76     private static final String JAVA_SE = "java.se";
  77 
  78     // the token for "all default modules"
  79     private static final String ALL_DEFAULT = "ALL-DEFAULT";
  80 
  81     // the token for "all unnamed modules"
  82     private static final String ALL_UNNAMED = "ALL-UNNAMED";
  83 
  84     // the token for "all system modules"
  85     private static final String ALL_SYSTEM = "ALL-SYSTEM";
  86 
  87     // the token for "all modules on the module path"
  88     private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
  89 
  90     // The ModulePatcher for the initial configuration
  91     private static final ModulePatcher patcher = initModulePatcher();
  92 
  93     // ModuleFinder for the initial configuration
  94     private static ModuleFinder initialFinder;
  95 
  96     /**
  97      * Returns the ModulePatcher for the initial configuration.
  98      */
  99     public static ModulePatcher patcher() {
 100         return patcher;
 101     }
 102 
 103     /**
 104      * Returns the ModuleFinder for the initial configuration
 105      */
 106     public static ModuleFinder finder() {
 107         assert initialFinder != null;
 108         return initialFinder;
 109     }
 110 
 111     /**
 112      * Initialize the module system, returning the boot Layer.
 113      *
 114      * @see java.lang.System#initPhase2()
 115      */
 116     public static Layer boot() {
 117 
 118         long t0 = System.nanoTime();
 119 
 120         // system modules (may be patched)
 121         ModuleFinder systemModules;
 122         if (SystemModules.MODULE_NAMES.length > 0) {
 123             systemModules = SystemModuleFinder.getInstance();
 124         } else {
 125             systemModules = ModuleFinder.ofSystem();
 126         }
 127 
 128         PerfCounters.systemModulesTime.addElapsedTimeFrom(t0);
 129 
 130 
 131         long t1 = System.nanoTime();
 132 
 133         // Once we have the system modules then we define the base module to
 134         // the VM. We do this here so that java.base is defined as early as
 135         // possible and also that resources in the base module can be located
 136         // for error messages that may happen from here on.
 137         ModuleReference base = systemModules.find(JAVA_BASE).orElse(null);
 138         if (base == null)
 139             throw new InternalError(JAVA_BASE + " not found");
 140         URI baseUri = base.location().orElse(null);
 141         if (baseUri == null)
 142             throw new InternalError(JAVA_BASE + " does not have a location");
 143         BootLoader.loadModule(base);
 144         Modules.defineModule(null, base.descriptor(), baseUri);
 145 
 146         PerfCounters.defineBaseTime.addElapsedTimeFrom(t1);
 147 
 148 
 149         long t2 = System.nanoTime();
 150 
 151         // --upgrade-module-path option specified to launcher
 152         ModuleFinder upgradeModulePath
 153             = createModulePathFinder("jdk.module.upgrade.path");
 154         if (upgradeModulePath != null)
 155             systemModules = ModuleFinder.compose(upgradeModulePath, systemModules);
 156 
 157         // --module-path option specified to the launcher
 158         ModuleFinder appModulePath = createModulePathFinder("jdk.module.path");
 159 
 160         // The module finder: [--upgrade-module-path] system [--module-path]
 161         ModuleFinder finder = systemModules;
 162         if (appModulePath != null)
 163             finder = ModuleFinder.compose(finder, appModulePath);
 164 
 165         // The root modules to resolve
 166         Set<String> roots = new HashSet<>();
 167 
 168         // launcher -m option to specify the main/initial module
 169         String mainModule = System.getProperty("jdk.module.main");
 170         if (mainModule != null)
 171             roots.add(mainModule);
 172 
 173         // additional module(s) specified by --add-modules
 174         boolean addAllDefaultModules = false;
 175         boolean addAllSystemModules = false;
 176         boolean addAllApplicationModules = false;
 177         for (String mod: getExtraAddModules()) {
 178             switch (mod) {
 179                 case ALL_DEFAULT:
 180                     addAllDefaultModules = true;
 181                     break;
 182                 case ALL_SYSTEM:
 183                     addAllSystemModules = true;
 184                     break;
 185                 case ALL_MODULE_PATH:
 186                     addAllApplicationModules = true;
 187                     break;
 188                 default :
 189                     roots.add(mod);
 190             }
 191         }
 192 
 193         // --limit-modules
 194         String propValue = getAndRemoveProperty("jdk.module.limitmods");
 195         if (propValue != null) {
 196             Set<String> mods = new HashSet<>();
 197             for (String mod: propValue.split(",")) {
 198                 mods.add(mod);
 199             }
 200             finder = limitFinder(finder, mods, roots);
 201         }
 202 
 203         // If there is no initial module specified then assume that the initial
 204         // module is the unnamed module of the application class loader. This
 205         // is implemented by resolving "java.se" and all (non-java.*) modules
 206         // that export an API. If "java.se" is not observable then all java.*
 207         // modules are resolved. Modules that have the DO_NOT_RESOLVE_BY_DEFAULT
 208         // bit set in their ModuleResolution attribute flags are excluded from
 209         // the default set of roots.
 210         if (mainModule == null || addAllDefaultModules) {
 211             boolean hasJava = false;
 212             if (systemModules.find(JAVA_SE).isPresent()) {
 213                 // java.se is a system module
 214                 if (finder == systemModules || finder.find(JAVA_SE).isPresent()) {
 215                     // java.se is observable
 216                     hasJava = true;
 217                     roots.add(JAVA_SE);
 218                 }
 219             }
 220 
 221             for (ModuleReference mref : systemModules.findAll()) {
 222                 String mn = mref.descriptor().name();
 223                 if (hasJava && mn.startsWith("java."))
 224                     continue;
 225 
 226                 if (ModuleResolution.doNotResolveByDefault(mref))
 227                     continue;
 228 
 229                 // add as root if observable and exports at least one package
 230                 if ((finder == systemModules || finder.find(mn).isPresent())) {
 231                     ModuleDescriptor descriptor = mref.descriptor();
 232                     for (ModuleDescriptor.Exports e : descriptor.exports()) {
 233                         if (!e.isQualified()) {
 234                             roots.add(mn);
 235                             break;
 236                         }
 237                     }
 238                 }
 239             }
 240         }
 241 
 242         // If `--add-modules ALL-SYSTEM` is specified then all observable system
 243         // modules will be resolved.
 244         if (addAllSystemModules) {
 245             ModuleFinder f = finder;  // observable modules
 246             systemModules.findAll()
 247                 .stream()
 248                 .filter(mref -> !ModuleResolution.doNotResolveByDefault(mref))
 249                 .map(ModuleReference::descriptor)
 250                 .map(ModuleDescriptor::name)
 251                 .filter(mn -> f.find(mn).isPresent())  // observable
 252                 .forEach(mn -> roots.add(mn));
 253         }
 254 
 255         // If `--add-modules ALL-MODULE-PATH` is specified then all observable
 256         // modules on the application module path will be resolved.
 257         if (appModulePath != null && addAllApplicationModules) {
 258             ModuleFinder f = finder;  // observable modules
 259             appModulePath.findAll()
 260                 .stream()
 261                 .map(ModuleReference::descriptor)
 262                 .map(ModuleDescriptor::name)
 263                 .filter(mn -> f.find(mn).isPresent())  // observable
 264                 .forEach(mn -> roots.add(mn));
 265         }
 266 
 267         PerfCounters.optionsAndRootsTime.addElapsedTimeFrom(t2);
 268 
 269 
 270         long t3 = System.nanoTime();
 271 
 272         // determine if post resolution checks are needed
 273         boolean needPostResolutionChecks = true;
 274         if (baseUri.getScheme().equals("jrt")   // toLowerCase not needed here
 275                 && (upgradeModulePath == null)
 276                 && (appModulePath == null)
 277                 && (patcher.isEmpty())) {
 278             needPostResolutionChecks = false;
 279         }
 280 
 281         PrintStream traceOutput = null;
 282         if (Boolean.getBoolean("jdk.launcher.traceResolver"))
 283             traceOutput = System.out;
 284 
 285         // run the resolver to create the configuration
 286         Configuration cf = SharedSecrets.getJavaLangModuleAccess()
 287                 .resolveAndBind(finder,
 288                                 roots,
 289                                 needPostResolutionChecks,
 290                                 traceOutput);
 291 
 292         // time to create configuration
 293         PerfCounters.resolveTime.addElapsedTimeFrom(t3);
 294 
 295         // check module names and incubating status
 296         checkModuleNamesAndStatus(cf);
 297 
 298         // mapping of modules to class loaders
 299         Function<String, ClassLoader> clf = ModuleLoaderMap.mappingFunction(cf);
 300 
 301         // check that all modules to be mapped to the boot loader will be
 302         // loaded from the runtime image
 303         if (needPostResolutionChecks) {
 304             for (ResolvedModule resolvedModule : cf.modules()) {
 305                 ModuleReference mref = resolvedModule.reference();
 306                 String name = mref.descriptor().name();
 307                 ClassLoader cl = clf.apply(name);
 308                 if (cl == null) {
 309 
 310                     if (upgradeModulePath != null
 311                             && upgradeModulePath.find(name).isPresent())
 312                         fail(name + ": cannot be loaded from upgrade module path");
 313 
 314                     if (!systemModules.find(name).isPresent())
 315                         fail(name + ": cannot be loaded from application module path");
 316                 }
 317             }
 318 
 319             // check if module specified in --patch-module is present
 320             for (String mn: patcher.patchedModules()) {
 321                 if (!cf.findModule(mn).isPresent()) {
 322                     warnUnknownModule(PATCH_MODULE, mn);
 323                 }
 324             }
 325         }
 326 
 327         // if needed check that there are no split packages in the set of
 328         // resolved modules for the boot layer
 329         if (SystemModules.hasSplitPackages() || needPostResolutionChecks) {
 330             Map<String, String> packageToModule = new HashMap<>();
 331             for (ResolvedModule resolvedModule : cf.modules()) {
 332                 ModuleDescriptor descriptor =
 333                     resolvedModule.reference().descriptor();
 334                 String name = descriptor.name();
 335                 for (String p : descriptor.packages()) {
 336                     String other = packageToModule.putIfAbsent(p, name);
 337                     if (other != null) {
 338                         fail("Package " + p + " in both module "
 339                              + name + " and module " + other);
 340                     }
 341                 }
 342             }
 343         }
 344 
 345         long t4 = System.nanoTime();
 346 
 347         // define modules to VM/runtime
 348         Layer bootLayer = Layer.empty().defineModules(cf, clf);
 349 
 350         PerfCounters.layerCreateTime.addElapsedTimeFrom(t4);
 351 
 352 
 353         long t5 = System.nanoTime();
 354 
 355         // define the module to its class loader, except java.base
 356         for (ResolvedModule resolvedModule : cf.modules()) {
 357             ModuleReference mref = resolvedModule.reference();
 358             String name = mref.descriptor().name();
 359             ClassLoader cl = clf.apply(name);
 360             if (cl == null) {
 361                 if (!name.equals(JAVA_BASE)) BootLoader.loadModule(mref);
 362             } else {
 363                 ((BuiltinClassLoader)cl).loadModule(mref);
 364             }
 365         }
 366 
 367         PerfCounters.loadModulesTime.addElapsedTimeFrom(t5);
 368 
 369 
 370         // --add-reads, -add-exports/-add-opens
 371         addExtraReads(bootLayer);
 372         addExtraExportsAndOpens(bootLayer);
 373 
 374         // total time to initialize
 375         PerfCounters.bootstrapTime.addElapsedTimeFrom(t0);
 376 
 377         // remember the ModuleFinder
 378         initialFinder = finder;
 379 
 380         return bootLayer;
 381     }
 382 
 383     /**
 384      * Returns a ModuleFinder that limits observability to the given root
 385      * modules, their transitive dependences, plus a set of other modules.
 386      */
 387     private static ModuleFinder limitFinder(ModuleFinder finder,
 388                                             Set<String> roots,
 389                                             Set<String> otherMods)
 390     {
 391         // resolve all root modules
 392         Configuration cf = Configuration.empty().resolve(finder,
 393                                                          ModuleFinder.of(),
 394                                                          roots);
 395 
 396         // module name -> reference
 397         Map<String, ModuleReference> map = new HashMap<>();
 398 
 399         // root modules and their transitive dependences
 400         cf.modules().stream()
 401             .map(ResolvedModule::reference)
 402             .forEach(mref -> map.put(mref.descriptor().name(), mref));
 403 
 404         // additional modules
 405         otherMods.stream()
 406             .map(finder::find)
 407             .flatMap(Optional::stream)
 408             .forEach(mref -> map.putIfAbsent(mref.descriptor().name(), mref));
 409 
 410         // set of modules that are observable
 411         Set<ModuleReference> mrefs = new HashSet<>(map.values());
 412 
 413         return new ModuleFinder() {
 414             @Override
 415             public Optional<ModuleReference> find(String name) {
 416                 return Optional.ofNullable(map.get(name));
 417             }
 418             @Override
 419             public Set<ModuleReference> findAll() {
 420                 return mrefs;
 421             }
 422         };
 423     }
 424 
 425     /**
 426      * Creates a finder from the module path that is the value of the given
 427      * system property.
 428      */
 429     private static ModuleFinder createModulePathFinder(String prop) {
 430         String s = System.getProperty(prop);
 431         if (s == null) {
 432             return null;
 433         } else {
 434             String[] dirs = s.split(File.pathSeparator);
 435             Path[] paths = new Path[dirs.length];
 436             int i = 0;
 437             for (String dir: dirs) {
 438                 paths[i++] = Paths.get(dir);
 439             }
 440             return ModuleFinder.of(paths);
 441         }
 442     }
 443 
 444 
 445     /**
 446      * Initialize the module patcher for the initial configuration passed on the
 447      * value of the --patch-module options.
 448      */
 449     private static ModulePatcher initModulePatcher() {
 450         Map<String, List<String>> map = decode("jdk.module.patch.",
 451                                                File.pathSeparator,
 452                                                false);
 453         return new ModulePatcher(map);
 454     }
 455 
 456     /**
 457      * Returns the set of module names specified via --add-modules options
 458      * on the command line
 459      */
 460     private static Set<String> getExtraAddModules() {
 461         String prefix = "jdk.module.addmods.";
 462         int index = 0;
 463 
 464         // the system property is removed after decoding
 465         String value = getAndRemoveProperty(prefix + index);
 466         if (value == null) {
 467             return Collections.emptySet();
 468         }
 469 
 470         Set<String> modules = new HashSet<>();
 471         while (value != null) {
 472             for (String s : value.split(",")) {
 473                 if (s.length() > 0) modules.add(s);
 474             }
 475             index++;
 476             value = getAndRemoveProperty(prefix + index);
 477         }
 478 
 479         return modules;
 480     }
 481 
 482     /**
 483      * Process the --add-reads options to add any additional read edges that
 484      * are specified on the command-line.
 485      */
 486     private static void addExtraReads(Layer bootLayer) {
 487 
 488         // decode the command line options
 489         Map<String, List<String>> map = decode("jdk.module.addreads.");
 490         if (map.isEmpty())
 491             return;
 492 
 493         for (Map.Entry<String, List<String>> e : map.entrySet()) {
 494 
 495             // the key is $MODULE
 496             String mn = e.getKey();
 497             Optional<Module> om = bootLayer.findModule(mn);
 498             if (!om.isPresent()) {
 499                 warnUnknownModule(ADD_READS, mn);
 500                 continue;
 501             }
 502             Module m = om.get();
 503 
 504             // the value is the set of other modules (by name)
 505             for (String name : e.getValue()) {
 506                 if (ALL_UNNAMED.equals(name)) {
 507                     Modules.addReadsAllUnnamed(m);
 508                 } else {
 509                     om = bootLayer.findModule(name);
 510                     if (om.isPresent()) {
 511                         Modules.addReads(m, om.get());
 512                     } else {
 513                         warnUnknownModule(ADD_READS, name);
 514                     }
 515                 }
 516             }
 517         }
 518     }
 519 
 520     /**
 521      * Process the --add-exports and --add-opens options to export/open
 522      * additional packages specified on the command-line.
 523      */
 524     private static void addExtraExportsAndOpens(Layer bootLayer) {
 525 
 526         // --add-exports
 527         String prefix = "jdk.module.addexports.";
 528         Map<String, List<String>> extraExports = decode(prefix);
 529         if (!extraExports.isEmpty()) {
 530             addExtraExportsOrOpens(bootLayer, extraExports, false);
 531         }
 532 
 533         // --add-opens
 534         prefix = "jdk.module.addopens.";
 535         Map<String, List<String>> extraOpens = decode(prefix);
 536         if (!extraOpens.isEmpty()) {
 537             addExtraExportsOrOpens(bootLayer, extraOpens, true);
 538         }
 539 
 540         // DEBUG_ADD_OPENS is for debugging purposes only
 541         String home = System.getProperty("java.home");
 542         Path file = Paths.get(home, "conf", "DEBUG_ADD_OPENS");
 543         if (Files.exists(file)) {
 544             warn(file + " detected; may break encapsulation");
 545             try (Stream<String> lines = Files.lines(file)) {
 546                 lines.map(line -> line.trim())
 547                     .filter(line -> (!line.isEmpty() && !line.startsWith("#")))
 548                     .forEach(line -> {
 549                         String[] s = line.split("/");
 550                         if (s.length != 2) {
 551                             fail("Unable to parse as <module>/<package>: " + line);
 552                         } else {
 553                             String mn = s[0];
 554                             String pkg = s[1];
 555                             openPackage(bootLayer, mn, pkg);
 556                         }
 557                     });
 558             } catch (IOException ioe) {
 559                 throw new UncheckedIOException(ioe);
 560             }
 561         }
 562     }
 563 
 564     private static void openPackage(Layer bootLayer, String mn, String pkg) {
 565         if (mn.equals("ALL-RESOLVED") && pkg.equals("ALL-PACKAGES")) {
 566             bootLayer.modules().stream().forEach(m ->
 567                 m.getDescriptor().packages().forEach(pn -> openPackage(m, pn)));
 568         } else {
 569             bootLayer.findModule(mn)
 570                      .filter(m -> m.getDescriptor().packages().contains(pkg))
 571                      .ifPresent(m -> openPackage(m, pkg));
 572         }
 573     }
 574 
 575     private static void openPackage(Module m, String pn) {
 576         Modules.addOpensToAllUnnamed(m, pn);
 577         warn("Opened for deep reflection: " + m.getName()  + "/" + pn);
 578     }
 579 
 580 
 581     private static void addExtraExportsOrOpens(Layer bootLayer,
 582                                                Map<String, List<String>> map,
 583                                                boolean opens)
 584     {
 585         String option = opens ? ADD_OPENS : ADD_EXPORTS;
 586         for (Map.Entry<String, List<String>> e : map.entrySet()) {
 587 
 588             // the key is $MODULE/$PACKAGE
 589             String key = e.getKey();
 590             String[] s = key.split("/");
 591             if (s.length != 2)
 592                 fail(unableToParse(option,  "<module>/<package>", key));
 593 
 594             String mn = s[0];
 595             String pn = s[1];
 596             if (mn.isEmpty() || pn.isEmpty())
 597                 fail(unableToParse(option,  "<module>/<package>", key));
 598 
 599             // The exporting module is in the boot layer
 600             Module m;
 601             Optional<Module> om = bootLayer.findModule(mn);
 602             if (!om.isPresent()) {
 603                 warnUnknownModule(option, mn);
 604                 continue;
 605             }
 606 
 607             m = om.get();
 608 
 609             if (!m.getDescriptor().packages().contains(pn)) {
 610                 warn("package " + pn + " not in " + mn);
 611                 continue;
 612             }
 613 
 614             // the value is the set of modules to export to (by name)
 615             for (String name : e.getValue()) {
 616                 boolean allUnnamed = false;
 617                 Module other = null;
 618                 if (ALL_UNNAMED.equals(name)) {
 619                     allUnnamed = true;
 620                 } else {
 621                     om = bootLayer.findModule(name);
 622                     if (om.isPresent()) {
 623                         other = om.get();
 624                     } else {
 625                         warnUnknownModule(option, name);
 626                         continue;
 627                     }
 628                 }
 629                 if (allUnnamed) {
 630                     if (opens) {
 631                         Modules.addOpensToAllUnnamed(m, pn);
 632                     } else {
 633                         Modules.addExportsToAllUnnamed(m, pn);
 634                     }
 635                 } else {
 636                     if (opens) {
 637                         Modules.addOpens(m, pn, other);
 638                     } else {
 639                         Modules.addExports(m, pn, other);
 640                     }
 641                 }
 642 
 643             }
 644         }
 645     }
 646 
 647     /**
 648      * Decodes the values of --add-reads, -add-exports, --add-opens or
 649      * --patch-modules options that are encoded in system properties.
 650      *
 651      * @param prefix the system property prefix
 652      * @praam regex the regex for splitting the RHS of the option value
 653      */
 654     private static Map<String, List<String>> decode(String prefix,
 655                                                     String regex,
 656                                                     boolean allowDuplicates) {
 657         int index = 0;
 658         // the system property is removed after decoding
 659         String value = getAndRemoveProperty(prefix + index);
 660         if (value == null)
 661             return Collections.emptyMap();
 662 
 663         Map<String, List<String>> map = new HashMap<>();
 664 
 665         while (value != null) {
 666 
 667             int pos = value.indexOf('=');
 668             if (pos == -1)
 669                 fail(unableToParse(option(prefix), "<module>=<value>", value));
 670             if (pos == 0)
 671                 fail(unableToParse(option(prefix), "<module>=<value>", value));
 672 
 673             // key is <module> or <module>/<package>
 674             String key = value.substring(0, pos);
 675 
 676             String rhs = value.substring(pos+1);
 677             if (rhs.isEmpty())
 678                 fail(unableToParse(option(prefix), "<module>=<value>", value));
 679 
 680             // value is <module>(,<module>)* or <file>(<pathsep><file>)*
 681             if (!allowDuplicates && map.containsKey(key))
 682                 fail(key + " specified more than once in " + option(prefix));
 683             List<String> values = map.computeIfAbsent(key, k -> new ArrayList<>());
 684             int ntargets = 0;
 685             for (String s : rhs.split(regex)) {
 686                 if (s.length() > 0) {
 687                     values.add(s);
 688                     ntargets++;
 689                 }
 690             }
 691             if (ntargets == 0)
 692                 fail("Target must be specified: " + option(prefix) + " " + value);
 693 
 694             index++;
 695             value = getAndRemoveProperty(prefix + index);
 696         }
 697 
 698         return map;
 699     }
 700 
 701     /**
 702      * Decodes the values of --add-reads, -add-exports or --add-opens
 703      * which use the "," to separate the RHS of the option value.
 704      */
 705     private static Map<String, List<String>> decode(String prefix) {
 706         return decode(prefix, ",", true);
 707     }
 708 
 709     /**
 710      * Gets and remove the named system property
 711      */
 712     private static String getAndRemoveProperty(String key) {
 713         return (String)System.getProperties().remove(key);
 714     }
 715 
 716     /**
 717      * Checks the names and resolution bit of each module in the configuration,
 718      * emitting warnings if needed.
 719      */
 720     private static void checkModuleNamesAndStatus(Configuration cf) {
 721         String incubating = null;
 722         for (ResolvedModule rm : cf.modules()) {
 723             ModuleReference mref = rm.reference();
 724             String mn = mref.descriptor().name();
 725 
 726             // emit warning if module name ends with a non-Java letter
 727             if (!Checks.hasLegalModuleNameLastCharacter(mn))
 728                 warn("Module name \"" + mn + "\" may soon be illegal");
 729 
 730             // emit warning if the WARN_INCUBATING module resolution bit set
 731             if (ModuleResolution.hasIncubatingWarning(mref)) {
 732                 if (incubating == null) {
 733                     incubating = mn;
 734                 } else {
 735                     incubating += ", " + mn;
 736                 }
 737             }
 738         }
 739         if (incubating != null)
 740             warn("Using incubator modules: " + incubating);
 741     }
 742 
 743     /**
 744      * Throws a RuntimeException with the given message
 745      */
 746     static void fail(String m) {
 747         throw new RuntimeException(m);
 748     }
 749 
 750     static void warn(String m) {
 751         System.err.println("WARNING: " + m);
 752     }
 753 
 754     static void warnUnknownModule(String option, String mn) {
 755         warn("Unknown module: " + mn + " specified in " + option);
 756     }
 757 
 758     static String unableToParse(String option, String text, String value) {
 759         return "Unable to parse " +  option + " " + text + ": " + value;
 760     }
 761 
 762     private static final String ADD_MODULES  = "--add-modules";
 763     private static final String ADD_EXPORTS  = "--add-exports";
 764     private static final String ADD_OPENS    = "--add-opens";
 765     private static final String ADD_READS    = "--add-reads";
 766     private static final String PATCH_MODULE = "--patch-module";
 767 
 768 
 769     /*
 770      * Returns the command-line option name corresponds to the specified
 771      * system property prefix.
 772      */
 773     static String option(String prefix) {
 774         switch (prefix) {
 775             case "jdk.module.addexports.":
 776                 return ADD_EXPORTS;
 777             case "jdk.module.addopens.":
 778                 return ADD_OPENS;
 779             case "jdk.module.addreads.":
 780                 return ADD_READS;
 781             case "jdk.module.patch.":
 782                 return PATCH_MODULE;
 783             case "jdk.module.addmods.":
 784                 return ADD_MODULES;
 785             default:
 786                 throw new IllegalArgumentException(prefix);
 787         }
 788     }
 789 
 790     static class PerfCounters {
 791 
 792         static PerfCounter systemModulesTime
 793             = PerfCounter.newPerfCounter("jdk.module.bootstrap.systemModulesTime");
 794         static PerfCounter defineBaseTime
 795             = PerfCounter.newPerfCounter("jdk.module.bootstrap.defineBaseTime");
 796         static PerfCounter optionsAndRootsTime
 797             = PerfCounter.newPerfCounter("jdk.module.bootstrap.optionsAndRootsTime");
 798         static PerfCounter resolveTime
 799             = PerfCounter.newPerfCounter("jdk.module.bootstrap.resolveTime");
 800         static PerfCounter layerCreateTime
 801             = PerfCounter.newPerfCounter("jdk.module.bootstrap.layerCreateTime");
 802         static PerfCounter loadModulesTime
 803             = PerfCounter.newPerfCounter("jdk.module.bootstrap.loadModulesTime");
 804         static PerfCounter bootstrapTime
 805             = PerfCounter.newPerfCounter("jdk.module.bootstrap.totalTime");
 806     }
 807 }