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