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.PrintStream;
  30 import java.lang.module.Configuration;
  31 import java.lang.module.ModuleDescriptor;
  32 import java.lang.module.ModuleFinder;
  33 import java.lang.module.ModuleReference;
  34 import java.lang.module.ResolvedModule;
  35 import java.lang.reflect.Layer;
  36 import java.lang.reflect.Module;
  37 import java.net.URI;
  38 import java.nio.file.Path;
  39 import java.nio.file.Paths;
  40 import java.util.Collections;
  41 import java.util.HashMap;
  42 import java.util.HashSet;
  43 import java.util.Map;
  44 import java.util.Optional;
  45 import java.util.Set;
  46 import java.util.function.Function;
  47 
  48 import jdk.internal.loader.BootLoader;
  49 import jdk.internal.loader.BuiltinClassLoader;
  50 import jdk.internal.misc.SharedSecrets;
  51 import jdk.internal.perf.PerfCounter;
  52 
  53 /**
  54  * Initializes/boots the module system.
  55  *
  56  * The {@link #boot() boot} method is called early in the startup to initialize
  57  * the module system. In summary, the boot method creates a Configuration by
  58  * resolving a set of module names specified via the launcher (or equivalent)
  59  * -m and -addmods options. The modules are located on a module path that is
  60  * constructed from the upgrade module path, system modules, and application
  61  * module path. The Configuration is instantiated as the boot Layer with each
  62  * module in the the configuration defined to one of the built-in class loaders.
  63  */
  64 
  65 public final class ModuleBootstrap {
  66     private ModuleBootstrap() { }
  67 
  68     private static final String JAVA_BASE = "java.base";
  69 
  70     private static final String JAVA_SE = "java.se";
  71 
  72     // the token for "all default modules"
  73     private static final String ALL_DEFAULT = "ALL-DEFAULT";
  74 
  75     // the token for "all unnamed modules"
  76     private static final String ALL_UNNAMED = "ALL-UNNAMED";
  77 
  78     // the token for "all system modules"
  79     private static final String ALL_SYSTEM = "ALL-SYSTEM";
  80 
  81     // the token for "all modules on the module path"
  82     private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
  83 
  84     // ModuleFinder for the initial configuration
  85     private static ModuleFinder initialFinder;
  86 
  87     /**
  88      * Returns the ModuleFinder for the initial configuration
  89      */
  90     public static ModuleFinder finder() {
  91         assert initialFinder != null;
  92         return initialFinder;
  93     }
  94 
  95     /**
  96      * Initialize the module system, returning the boot Layer.
  97      *
  98      * @see java.lang.System#initPhase2()
  99      */
 100     public static Layer boot() {
 101 
 102         long t0 = System.nanoTime();
 103 
 104         // system modules
 105         ModuleFinder systemModules = ModuleFinder.ofSystem();
 106 
 107         PerfCounters.systemModulesTime.addElapsedTimeFrom(t0);
 108 
 109 
 110         long t1 = System.nanoTime();
 111 
 112         // Once we have the system modules then we define the base module to
 113         // the VM. We do this here so that java.base is defined as early as
 114         // possible and also that resources in the base module can be located
 115         // for error messages that may happen from here on.
 116         ModuleReference base = systemModules.find(JAVA_BASE).orElse(null);
 117         if (base == null)
 118             throw new InternalError(JAVA_BASE + " not found");
 119         URI baseUri = base.location().orElse(null);
 120         if (baseUri == null)
 121             throw new InternalError(JAVA_BASE + " does not have a location");
 122         BootLoader.loadModule(base);
 123         Modules.defineModule(null, base.descriptor(), baseUri);
 124 
 125         PerfCounters.defineBaseTime.addElapsedTimeFrom(t1);
 126 
 127 
 128         long t2 = System.nanoTime();
 129 
 130         // -upgrademodulepath option specified to launcher
 131         ModuleFinder upgradeModulePath
 132             = createModulePathFinder("jdk.upgrade.module.path");
 133         if (upgradeModulePath != null)
 134             systemModules = ModuleFinder.compose(upgradeModulePath, systemModules);
 135 
 136         // -modulepath option specified to the launcher
 137         ModuleFinder appModulePath = createModulePathFinder("jdk.module.path");
 138 
 139         // The module finder: [-upgrademodulepath] system [-modulepath]
 140         ModuleFinder finder = systemModules;
 141         if (appModulePath != null)
 142             finder = ModuleFinder.compose(finder, appModulePath);
 143 
 144         // The root modules to resolve
 145         Set<String> roots = new HashSet<>();
 146 
 147         // launcher -m option to specify the main/initial module
 148         String mainModule = System.getProperty("jdk.module.main");
 149         if (mainModule != null)
 150             roots.add(mainModule);
 151 
 152         // additional module(s) specified by -addmods
 153         boolean addAllDefaultModules = false;
 154         boolean addAllSystemModules = false;
 155         boolean addAllApplicationModules = false;
 156         String propValue = System.getProperty("jdk.launcher.addmods");
 157         if (propValue != null) {
 158             for (String mod: propValue.split(",")) {
 159                 switch (mod) {
 160                     case ALL_DEFAULT:
 161                         addAllDefaultModules = true;
 162                         break;
 163                     case ALL_SYSTEM:
 164                         addAllSystemModules = true;
 165                         break;
 166                     case ALL_MODULE_PATH:
 167                         addAllApplicationModules = true;
 168                         break;
 169                     default :
 170                         roots.add(mod);
 171                 }
 172             }
 173         }
 174 
 175         // -limitmods
 176         propValue = System.getProperty("jdk.launcher.limitmods");
 177         if (propValue != null) {
 178             Set<String> mods = new HashSet<>();
 179             for (String mod: propValue.split(",")) {
 180                 mods.add(mod);
 181             }
 182             finder = limitFinder(finder, mods, roots);
 183         }
 184 
 185         // If there is no initial module specified then assume that the initial
 186         // module is the unnamed module of the application class loader. This
 187         // is implemented by resolving "java.se" and all (non-java.*) modules
 188         // that export an API. If "java.se" is not observable then all java.*
 189         // modules are resolved.
 190         if (mainModule == null || addAllDefaultModules) {
 191             boolean hasJava = false;
 192             if (systemModules.find(JAVA_SE).isPresent()) {
 193                 // java.se is a system module
 194                 if (finder == systemModules || finder.find(JAVA_SE).isPresent()) {
 195                     // java.se is observable
 196                     hasJava = true;
 197                     roots.add(JAVA_SE);
 198                 }
 199             }
 200 
 201             for (ModuleReference mref : systemModules.findAll()) {
 202                 String mn = mref.descriptor().name();
 203                 if (hasJava && mn.startsWith("java."))
 204                     continue;
 205 
 206                 // add as root if observable and exports at least one package
 207                 if ((finder == systemModules || finder.find(mn).isPresent())) {
 208                     ModuleDescriptor descriptor = mref.descriptor();
 209                     for (ModuleDescriptor.Exports e : descriptor.exports()) {
 210                         if (!e.isQualified()) {
 211                             roots.add(mn);
 212                             break;
 213                         }
 214                     }
 215                 }
 216             }
 217         }
 218 
 219         // If `-addmods ALL-SYSTEM` is specified then all observable system
 220         // modules will be resolved.
 221         if (addAllSystemModules) {
 222             ModuleFinder f = finder;  // observable modules
 223             systemModules.findAll()
 224                 .stream()
 225                 .map(ModuleReference::descriptor)
 226                 .map(ModuleDescriptor::name)
 227                 .filter(mn -> f.find(mn).isPresent())  // observable
 228                 .forEach(mn -> roots.add(mn));
 229         }
 230 
 231         // If `-addmods ALL-MODULE-PATH` is specified then all observable
 232         // modules on the application module path will be resolved.
 233         if  (appModulePath != null && addAllApplicationModules) {
 234             ModuleFinder f = finder;  // observable modules
 235             appModulePath.findAll()
 236                 .stream()
 237                 .map(ModuleReference::descriptor)
 238                 .map(ModuleDescriptor::name)
 239                 .filter(mn -> f.find(mn).isPresent())  // observable
 240                 .forEach(mn -> roots.add(mn));
 241         }
 242 
 243         PerfCounters.optionsAndRootsTime.addElapsedTimeFrom(t2);
 244 
 245 
 246         long t3 = System.nanoTime();
 247 
 248         // determine if post resolution checks are needed
 249         boolean needPostResolutionChecks = true;
 250         if (baseUri.getScheme().equals("jrt")   // toLowerCase not needed here
 251                 && (upgradeModulePath == null)
 252                 && (appModulePath == null)
 253                 && (System.getProperty("jdk.launcher.patch.0") == null)) {
 254             needPostResolutionChecks = false;
 255         }
 256 
 257         PrintStream traceOutput = null;
 258         if (Boolean.getBoolean("jdk.launcher.traceResolver"))
 259             traceOutput = System.out;
 260 
 261         // run the resolver to create the configuration
 262         Configuration cf = SharedSecrets.getJavaLangModuleAccess()
 263                 .resolveRequiresAndUses(finder,
 264                                         roots,
 265                                         needPostResolutionChecks,
 266                                         traceOutput);
 267 
 268         // time to create configuration
 269         PerfCounters.resolveTime.addElapsedTimeFrom(t3);
 270 
 271 
 272         // mapping of modules to class loaders
 273         Function<String, ClassLoader> clf = ModuleLoaderMap.mappingFunction(cf);
 274 
 275         // check that all modules to be mapped to the boot loader will be
 276         // loaded from the runtime image
 277         if (needPostResolutionChecks) {
 278             for (ResolvedModule resolvedModule : cf.modules()) {
 279                 ModuleReference mref = resolvedModule.reference();
 280                 String name = mref.descriptor().name();
 281                 ClassLoader cl = clf.apply(name);
 282                 if (cl == null) {
 283 
 284                     if (upgradeModulePath != null
 285                             && upgradeModulePath.find(name).isPresent())
 286                         fail(name + ": cannot be loaded from upgrade module path");
 287 
 288                     if (!systemModules.find(name).isPresent())
 289                         fail(name + ": cannot be loaded from application module path");
 290                 }
 291             }
 292         }
 293 
 294 
 295         long t4 = System.nanoTime();
 296 
 297         // define modules to VM/runtime
 298         Layer bootLayer = Layer.empty().defineModules(cf, clf);
 299 
 300         PerfCounters.layerCreateTime.addElapsedTimeFrom(t4);
 301 
 302 
 303         long t5 = System.nanoTime();
 304 
 305         // define the module to its class loader, except java.base
 306         for (ResolvedModule resolvedModule : cf.modules()) {
 307             ModuleReference mref = resolvedModule.reference();
 308             String name = mref.descriptor().name();
 309             ClassLoader cl = clf.apply(name);
 310             if (cl == null) {
 311                 if (!name.equals(JAVA_BASE)) BootLoader.loadModule(mref);
 312             } else {
 313                 ((BuiltinClassLoader)cl).loadModule(mref);
 314             }
 315         }
 316 
 317         PerfCounters.loadModulesTime.addElapsedTimeFrom(t5);
 318 
 319 
 320         // -XaddReads and -XaddExports
 321         addExtraReads(bootLayer);
 322         addExtraExports(bootLayer);
 323 
 324         // total time to initialize
 325         PerfCounters.bootstrapTime.addElapsedTimeFrom(t0);
 326 
 327         // remember the ModuleFinder
 328         initialFinder = finder;
 329 
 330         return bootLayer;
 331     }
 332 
 333     /**
 334      * Returns a ModuleFinder that limits observability to the given root
 335      * modules, their transitive dependences, plus a set of other modules.
 336      */
 337     private static ModuleFinder limitFinder(ModuleFinder finder,
 338                                             Set<String> roots,
 339                                             Set<String> otherMods)
 340     {
 341         // resolve all root modules
 342         Configuration cf = Configuration.empty()
 343                 .resolveRequires(finder,
 344                                  ModuleFinder.of(),
 345                                  roots);
 346 
 347         // module name -> reference
 348         Map<String, ModuleReference> map = new HashMap<>();
 349 
 350         // root modules and their transitive dependences
 351         cf.modules().stream()
 352             .map(ResolvedModule::reference)
 353             .forEach(mref -> map.put(mref.descriptor().name(), mref));
 354 
 355         // additional modules
 356         otherMods.stream()
 357             .map(finder::find)
 358             .flatMap(Optional::stream)
 359             .forEach(mref -> map.putIfAbsent(mref.descriptor().name(), mref));
 360 
 361         // set of modules that are observable
 362         Set<ModuleReference> mrefs = new HashSet<>(map.values());
 363 
 364         return new ModuleFinder() {
 365             @Override
 366             public Optional<ModuleReference> find(String name) {
 367                 return Optional.ofNullable(map.get(name));
 368             }
 369             @Override
 370             public Set<ModuleReference> findAll() {
 371                 return mrefs;
 372             }
 373         };
 374     }
 375 
 376     /**
 377      * Creates a finder from the module path that is the value of the given
 378      * system property.
 379      */
 380     private static ModuleFinder createModulePathFinder(String prop) {
 381         String s = System.getProperty(prop);
 382         if (s == null) {
 383             return null;
 384         } else {
 385             String[] dirs = s.split(File.pathSeparator);
 386             Path[] paths = new Path[dirs.length];
 387             int i = 0;
 388             for (String dir: dirs) {
 389                 paths[i++] = Paths.get(dir);
 390             }
 391             return ModuleFinder.of(paths);
 392         }
 393     }
 394 
 395 
 396     /**
 397      * Process the -XaddReads options to add any additional read edges that
 398      * are specified on the command-line.
 399      */
 400     private static void addExtraReads(Layer bootLayer) {
 401 
 402         // decode the command line options
 403         Map<String, Set<String>> map = decode("jdk.launcher.addreads.");
 404 
 405         for (Map.Entry<String, Set<String>> e : map.entrySet()) {
 406 
 407             // the key is $MODULE
 408             String mn = e.getKey();
 409             Optional<Module> om = bootLayer.findModule(mn);
 410             if (!om.isPresent())
 411                 fail("Unknown module: " + mn);
 412             Module m = om.get();
 413 
 414             // the value is the set of other modules (by name)
 415             for (String name : e.getValue()) {
 416 
 417                 Module other;
 418                 if (ALL_UNNAMED.equals(name)) {
 419                     Modules.addReadsAllUnnamed(m);
 420                 } else {
 421                     om = bootLayer.findModule(name);
 422                     if (!om.isPresent())
 423                         fail("Unknown module: " + name);
 424                     other = om.get();
 425                     Modules.addReads(m, other);
 426                 }
 427 
 428             }
 429         }
 430     }
 431 
 432 
 433     /**
 434      * Process the -XaddExports options to add any additional read edges that
 435      * are specified on the command-line.
 436      */
 437     private static void addExtraExports(Layer bootLayer) {
 438 
 439         // decode the command line options
 440         Map<String, Set<String>> map = decode("jdk.launcher.addexports.");
 441 
 442         for (Map.Entry<String, Set<String>> e : map.entrySet()) {
 443 
 444             // the key is $MODULE/$PACKAGE
 445             String key = e.getKey();
 446             String[] s = key.split("/");
 447             if (s.length != 2)
 448                 fail("Unable to parse: " + key);
 449 
 450             String mn = s[0];
 451             String pn = s[1];
 452 
 453             // The exporting module is in the boot layer
 454             Module m;
 455             Optional<Module> om = bootLayer.findModule(mn);
 456             if (!om.isPresent())
 457                 fail("Unknown module: " + mn);
 458             m = om.get();
 459 
 460             // the value is the set of modules to export to (by name)
 461             for (String name : e.getValue()) {
 462                 boolean allUnnamed = false;
 463                 Module other = null;
 464                 if (ALL_UNNAMED.equals(name)) {
 465                     allUnnamed = true;
 466                 } else {
 467                     om = bootLayer.findModule(name);
 468                     if (om.isPresent()) {
 469                         other = om.get();
 470                     } else {
 471                         fail("Unknown module: " + name);
 472                     }
 473                 }
 474 
 475                 if (allUnnamed) {
 476                     Modules.addExportsToAllUnnamed(m, pn);
 477                 } else {
 478                     Modules.addExports(m, pn, other);
 479                 }
 480             }
 481         }
 482     }
 483 
 484 
 485     /**
 486      * Decodes the values of -XaddReads or -XaddExports options
 487      *
 488      * The format of the options is: $KEY=$MODULE(,$MODULE)*
 489      */
 490     private static Map<String, Set<String>> decode(String prefix) {
 491         int index = 0;
 492         String value = System.getProperty(prefix + index);
 493         if (value == null)
 494             return Collections.emptyMap();
 495 
 496         Map<String, Set<String>> map = new HashMap<>();
 497 
 498         while (value != null) {
 499 
 500             int pos = value.indexOf('=');
 501             if (pos == -1)
 502                 fail("Unable to parse: " + value);
 503             if (pos == 0)
 504                 fail("Missing module name in: " + value);
 505 
 506             // key is <module> or <module>/<package>
 507             String key = value.substring(0, pos);
 508 
 509             String rhs = value.substring(pos+1);
 510             if (rhs.isEmpty())
 511                 fail("Unable to parse: " + value);
 512 
 513 
 514             // value is <module>(,<module>)*
 515             if (map.containsKey(key))
 516                 fail(key + " specified more than once");
 517 
 518             Set<String> values = new HashSet<>();
 519             map.put(key, values);
 520             for (String s : rhs.split(",")) {
 521                 if (s.length() > 0) values.add(s);
 522             }
 523 
 524             index++;
 525             value = System.getProperty(prefix + index);
 526         }
 527 
 528         return map;
 529     }
 530 
 531 
 532     /**
 533      * Throws a RuntimeException with the given message
 534      */
 535     static void fail(String m) {
 536         throw new RuntimeException(m);
 537     }
 538 
 539     static class PerfCounters {
 540 
 541         static PerfCounter systemModulesTime
 542             = PerfCounter.newPerfCounter("jdk.module.bootstrap.systemModulesTime");
 543         static PerfCounter defineBaseTime
 544             = PerfCounter.newPerfCounter("jdk.module.bootstrap.defineBaseTime");
 545         static PerfCounter optionsAndRootsTime
 546             = PerfCounter.newPerfCounter("jdk.module.bootstrap.optionsAndRootsTime");
 547         static PerfCounter resolveTime
 548             = PerfCounter.newPerfCounter("jdk.module.bootstrap.resolveTime");
 549         static PerfCounter layerCreateTime
 550             = PerfCounter.newPerfCounter("jdk.module.bootstrap.layerCreateTime");
 551         static PerfCounter loadModulesTime
 552             = PerfCounter.newPerfCounter("jdk.module.bootstrap.loadModulesTime");
 553         static PerfCounter bootstrapTime
 554             = PerfCounter.newPerfCounter("jdk.module.bootstrap.totalTime");
 555     }
 556 }