1 /* 2 * Copyright (c) 2015, 2025, 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 package jdk.internal.module; 26 27 import java.io.ByteArrayInputStream; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.io.UncheckedIOException; 31 import java.lang.module.ModuleDescriptor; 32 import java.lang.module.ModuleFinder; 33 import java.lang.module.ModuleReader; 34 import java.lang.module.ModuleReference; 35 import java.lang.reflect.Constructor; 36 import java.net.URI; 37 import java.nio.ByteBuffer; 38 import java.nio.file.Files; 39 import java.nio.file.Path; 40 import java.util.ArrayDeque; 41 import java.util.Collections; 42 import java.util.Deque; 43 import java.util.HashMap; 44 import java.util.HashSet; 45 import java.util.Iterator; 46 import java.util.Map; 47 import java.util.Objects; 48 import java.util.Optional; 49 import java.util.Set; 50 import java.util.Spliterator; 51 import java.util.function.Consumer; 52 import java.util.function.Supplier; 53 import java.util.stream.Stream; 54 import java.util.stream.StreamSupport; 55 56 import jdk.internal.jimage.ImageReader; 57 import jdk.internal.jimage.ImageReaderFactory; 58 import jdk.internal.access.JavaNetUriAccess; 59 import jdk.internal.access.SharedSecrets; 60 import jdk.internal.util.StaticProperty; 61 import jdk.internal.module.ModuleHashes.HashSupplier; 62 63 /** 64 * The factory for SystemModules objects and for creating ModuleFinder objects 65 * that find modules in the runtime image. 66 * 67 * This class supports initializing the module system when the runtime is an 68 * images build, an exploded build, or an images build with java.base patched 69 * by an exploded java.base. It also supports a testing mode that re-parses 70 * the module-info.class resources in the run-time image. 71 */ 72 73 public final class SystemModuleFinders { 74 private static final JavaNetUriAccess JNUA = SharedSecrets.getJavaNetUriAccess(); 75 76 private static final boolean USE_FAST_PATH; 77 static { 78 String value = System.getProperty("jdk.system.module.finder.disableFastPath"); 79 if (value == null) { 80 USE_FAST_PATH = true; 81 } else { 82 USE_FAST_PATH = !value.isEmpty() && !Boolean.parseBoolean(value); 83 } 84 } 85 86 // cached ModuleFinder returned from ofSystem 87 private static volatile ModuleFinder cachedSystemModuleFinder; 88 89 private SystemModuleFinders() { } 90 91 /** 92 * Returns the SystemModules object to reconstitute all modules. Returns 93 * null if this is an exploded build or java.base is patched by an exploded 94 * build. 95 */ 96 static SystemModules allSystemModules() { 97 if (USE_FAST_PATH) { 98 return SystemModulesMap.allSystemModules(); 99 } else { 100 return null; 101 } 102 } 103 104 /** 105 * Returns a SystemModules object to reconstitute the modules for the 106 * given initial module. If the initial module is null then return the 107 * SystemModules object to reconstitute the default modules. 108 * 109 * Return null if there is no SystemModules class for the initial module, 110 * this is an exploded build, or java.base is patched by an exploded build. 111 */ 112 static SystemModules systemModules(String initialModule) { 113 if (USE_FAST_PATH) { 114 if (initialModule == null) { 115 return SystemModulesMap.defaultSystemModules(); 116 } 117 118 String[] initialModules = SystemModulesMap.moduleNames(); 119 for (int i = 0; i < initialModules.length; i++) { 120 String moduleName = initialModules[i]; 121 if (initialModule.equals(moduleName)) { 122 String cn = SystemModulesMap.classNames()[i]; 123 try { 124 // one-arg Class.forName as java.base may not be defined 125 Constructor<?> ctor = Class.forName(cn).getConstructor(); 126 return (SystemModules) ctor.newInstance(); 127 } catch (Exception e) { 128 throw new InternalError(e); 129 } 130 } 131 } 132 } 133 return null; 134 } 135 136 /** 137 * Returns a ModuleFinder that is backed by the given SystemModules object. 138 * 139 * @apiNote The returned ModuleFinder is thread safe. 140 */ 141 static ModuleFinder of(SystemModules systemModules) { 142 ModuleDescriptor[] descriptors = systemModules.moduleDescriptors(); 143 ModuleTarget[] targets = systemModules.moduleTargets(); 144 ModuleHashes[] recordedHashes = systemModules.moduleHashes(); 145 ModuleResolution[] moduleResolutions = systemModules.moduleResolutions(); 146 147 int moduleCount = descriptors.length; 148 ModuleReference[] mrefs = new ModuleReference[moduleCount]; 149 @SuppressWarnings(value = {"rawtypes", "unchecked"}) 150 Map.Entry<String, ModuleReference>[] map 151 = (Map.Entry<String, ModuleReference>[])new Map.Entry[moduleCount]; 152 153 Map<String, byte[]> nameToHash = generateNameToHash(recordedHashes); 154 155 for (int i = 0; i < moduleCount; i++) { 156 String name = descriptors[i].name(); 157 HashSupplier hashSupplier = hashSupplier(nameToHash, name); 158 ModuleReference mref = toModuleReference(descriptors[i], 159 targets[i], 160 recordedHashes[i], 161 hashSupplier, 162 moduleResolutions[i]); 163 mrefs[i] = mref; 164 map[i] = Map.entry(name, mref); 165 } 166 167 return new SystemModuleFinder(mrefs, map); 168 } 169 170 /** 171 * Returns the ModuleFinder to find all system modules. Supports both 172 * images and exploded builds. 173 * 174 * @apiNote Used by ModuleFinder.ofSystem() 175 */ 176 public static ModuleFinder ofSystem() { 177 ModuleFinder finder = cachedSystemModuleFinder; 178 if (finder != null) { 179 return finder; 180 } 181 182 // probe to see if this is an images build 183 String home = StaticProperty.javaHome(); 184 Path modules = Path.of(home, "lib", "modules"); 185 if (Files.isRegularFile(modules)) { 186 if (USE_FAST_PATH) { 187 SystemModules systemModules = allSystemModules(); 188 if (systemModules != null) { 189 finder = of(systemModules); 190 } 191 } 192 193 // fall back to parsing the module-info.class files in image 194 if (finder == null) { 195 finder = ofModuleInfos(); 196 } 197 198 cachedSystemModuleFinder = finder; 199 return finder; 200 201 } 202 203 // exploded build (do not cache module finder) 204 Path dir = Path.of(home, "modules"); 205 if (!Files.isDirectory(dir)) 206 throw new InternalError("Unable to detect the run-time image"); 207 return ModulePath.of(ModuleBootstrap.patcher(), dir); 208 } 209 210 /** 211 * Parses the {@code module-info.class} of all modules in the runtime image and 212 * returns a ModuleFinder to find the modules. 213 * 214 * @apiNote The returned ModuleFinder is thread safe. 215 */ 216 private static ModuleFinder ofModuleInfos() { 217 // parse the module-info.class in every module 218 Map<String, ModuleInfo.Attributes> nameToAttributes = new HashMap<>(); 219 Map<String, byte[]> nameToHash = new HashMap<>(); 220 221 allModuleAttributes().forEach(attrs -> { 222 nameToAttributes.put(attrs.descriptor().name(), attrs); 223 ModuleHashes hashes = attrs.recordedHashes(); 224 if (hashes != null) { 225 for (String name : hashes.names()) { 226 nameToHash.computeIfAbsent(name, k -> hashes.hashFor(name)); 227 } 228 } 229 }); 230 231 // create a ModuleReference for each module 232 Set<ModuleReference> mrefs = new HashSet<>(); 233 Map<String, ModuleReference> nameToModule = new HashMap<>(); 234 for (Map.Entry<String, ModuleInfo.Attributes> e : nameToAttributes.entrySet()) { 235 String mn = e.getKey(); 236 ModuleInfo.Attributes attrs = e.getValue(); 237 HashSupplier hashSupplier = hashSupplier(nameToHash, mn); 238 ModuleReference mref = toModuleReference(attrs.descriptor(), 239 attrs.target(), 240 attrs.recordedHashes(), 241 hashSupplier, 242 attrs.moduleResolution()); 243 mrefs.add(mref); 244 nameToModule.put(mn, mref); 245 } 246 247 return new SystemModuleFinder(mrefs, nameToModule); 248 } 249 250 /** 251 * Parses the {@code module-info.class} of all modules in the runtime image and 252 * returns a stream of {@link ModuleInfo.Attributes Attributes} for them. The 253 * returned attributes are in no specific order. 254 */ 255 private static Stream<ModuleInfo.Attributes> allModuleAttributes() { 256 // System-wide image reader. 257 ImageReader reader = SystemImage.reader(); 258 try { 259 return reader.findNode("/modules") 260 .getChildNames() 261 .map(mn -> readModuleAttributes(reader, mn)); 262 } catch (IOException e) { 263 throw new Error("Error reading root /modules entry", e); 264 } 265 } 266 267 /** 268 * Returns the module's "module-info", returning a holder for its class file 269 * attributes. Every module is required to have a valid {@code module-info.class}. 270 */ 271 private static ModuleInfo.Attributes readModuleAttributes(ImageReader reader, String moduleName) { 272 Exception err = null; 273 try { 274 ImageReader.Node node = reader.findNode(moduleName + "/module-info.class"); 275 if (node != null && node.isResource()) { 276 return ModuleInfo.read(reader.getResourceBuffer(node), null); 277 } 278 } catch (IOException | UncheckedIOException e) { 279 err = e; 280 } 281 throw new Error("Missing or invalid module-info.class for module: " + moduleName, err); 282 } 283 284 /** 285 * A ModuleFinder that finds module in an array or set of modules. 286 */ 287 private static class SystemModuleFinder implements ModuleFinder { 288 final Set<ModuleReference> mrefs; 289 final Map<String, ModuleReference> nameToModule; 290 291 SystemModuleFinder(ModuleReference[] array, 292 Map.Entry<String, ModuleReference>[] map) { 293 this.mrefs = Set.of(array); 294 this.nameToModule = Map.ofEntries(map); 295 } 296 297 SystemModuleFinder(Set<ModuleReference> mrefs, 298 Map<String, ModuleReference> nameToModule) { 299 this.mrefs = Set.copyOf(mrefs); 300 this.nameToModule = Map.copyOf(nameToModule); 301 } 302 303 @Override 304 public Optional<ModuleReference> find(String name) { 305 Objects.requireNonNull(name); 306 return Optional.ofNullable(nameToModule.get(name)); 307 } 308 309 @Override 310 public Set<ModuleReference> findAll() { 311 return mrefs; 312 } 313 } 314 315 /** 316 * Creates a ModuleReference to the system module. 317 */ 318 static ModuleReference toModuleReference(ModuleDescriptor descriptor, 319 ModuleTarget target, 320 ModuleHashes recordedHashes, 321 HashSupplier hasher, 322 ModuleResolution mres) { 323 String mn = descriptor.name(); 324 URI uri = JNUA.create("jrt", "/".concat(mn)); 325 326 Supplier<ModuleReader> readerSupplier = new Supplier<>() { 327 @Override 328 public ModuleReader get() { 329 return new SystemModuleReader(mn); 330 } 331 }; 332 333 ModuleReference mref = new ModuleReferenceImpl(descriptor, 334 uri, 335 readerSupplier, 336 null, 337 target, 338 recordedHashes, 339 hasher, 340 mres); 341 342 // may need a reference to a patched module if --patch-module specified 343 mref = ModuleBootstrap.patcher().patchIfNeeded(mref); 344 345 return mref; 346 } 347 348 /** 349 * Generates a map of module name to hash value. 350 */ 351 static Map<String, byte[]> generateNameToHash(ModuleHashes[] recordedHashes) { 352 Map<String, byte[]> nameToHash = null; 353 354 boolean secondSeen = false; 355 // record the hashes to build HashSupplier 356 for (ModuleHashes mh : recordedHashes) { 357 if (mh != null) { 358 // if only one module contain ModuleHashes, use it 359 if (nameToHash == null) { 360 nameToHash = mh.hashes(); 361 } else { 362 if (!secondSeen) { 363 nameToHash = new HashMap<>(nameToHash); 364 secondSeen = true; 365 } 366 nameToHash.putAll(mh.hashes()); 367 } 368 } 369 } 370 return (nameToHash != null) ? nameToHash : Map.of(); 371 } 372 373 /** 374 * Returns a HashSupplier that returns the hash of the given module. 375 */ 376 static HashSupplier hashSupplier(Map<String, byte[]> nameToHash, String name) { 377 byte[] hash = nameToHash.get(name); 378 if (hash != null) { 379 // avoid lambda here 380 return new HashSupplier() { 381 @Override 382 public byte[] generate(String algorithm) { 383 return hash; 384 } 385 }; 386 } else { 387 return null; 388 } 389 } 390 391 /** 392 * Holder class for the ImageReader. 393 */ 394 private static class SystemImage { 395 static final ImageReader READER = ImageReaderFactory.getImageReader(); 396 static ImageReader reader() { 397 return READER; 398 } 399 } 400 401 /** 402 * A ModuleReader for reading resources from a module linked into the 403 * run-time image. 404 */ 405 private static class SystemModuleReader implements ModuleReader { 406 private final String module; 407 private volatile boolean closed; 408 409 SystemModuleReader(String module) { 410 this.module = module; 411 } 412 413 /** 414 * Returns {@code true} if the given resource exists, {@code false} 415 * if not found. 416 */ 417 private boolean containsResource(String module, String name) throws IOException { 418 Objects.requireNonNull(name); 419 if (closed) 420 throw new IOException("ModuleReader is closed"); 421 ImageReader imageReader = SystemImage.reader(); 422 return imageReader != null && imageReader.containsResource(module, name); 423 } 424 425 @Override 426 public Optional<URI> find(String name) throws IOException { 427 Objects.requireNonNull(name); 428 if (containsResource(module, name)) { 429 URI u = JNUA.create("jrt", "/" + module + "/" + name); 430 return Optional.of(u); 431 } else { 432 return Optional.empty(); 433 } 434 } 435 436 @Override 437 public Optional<InputStream> open(String name) throws IOException { 438 return read(name).map(this::toInputStream); 439 } 440 441 private InputStream toInputStream(ByteBuffer bb) { // ## -> ByteBuffer? 442 try { 443 int rem = bb.remaining(); 444 byte[] bytes = new byte[rem]; 445 bb.get(bytes); 446 return new ByteArrayInputStream(bytes); 447 } finally { 448 release(bb); 449 } 450 } 451 452 /** 453 * Returns the node for the given resource if found. If the name references 454 * a non-resource node, then {@code null} is returned. 455 */ 456 private ImageReader.Node findResource(ImageReader reader, String name) throws IOException { 457 Objects.requireNonNull(name); 458 if (closed) { 459 throw new IOException("ModuleReader is closed"); 460 } 461 return reader.findResourceNode(module, name); 462 } 463 464 @Override 465 public Optional<ByteBuffer> read(String name) throws IOException { 466 ImageReader reader = SystemImage.reader(); 467 return Optional.ofNullable(findResource(reader, name)) 468 .map(reader::getResourceBuffer); 469 } 470 471 @Override 472 public void release(ByteBuffer bb) { 473 Objects.requireNonNull(bb); 474 ImageReader.releaseByteBuffer(bb); 475 } 476 477 @Override 478 public Stream<String> list() throws IOException { 479 if (closed) 480 throw new IOException("ModuleReader is closed"); 481 482 Spliterator<String> s = new ModuleContentSpliterator(module); 483 return StreamSupport.stream(s, false); 484 } 485 486 @Override 487 public void close() { 488 // nothing else to do 489 closed = true; 490 } 491 } 492 493 /** 494 * A Spliterator for traversing the resources of a module linked into the 495 * run-time image. 496 */ 497 private static class ModuleContentSpliterator implements Spliterator<String> { 498 final String moduleRoot; 499 final Deque<ImageReader.Node> stack; 500 Iterator<String> iterator; 501 502 ModuleContentSpliterator(String module) throws IOException { 503 moduleRoot = "/modules/" + module; 504 stack = new ArrayDeque<>(); 505 506 // push the root node to the stack to get started 507 ImageReader.Node dir = SystemImage.reader().findNode(moduleRoot); 508 if (dir == null || !dir.isDirectory()) 509 throw new IOException(moduleRoot + " not a directory"); 510 stack.push(dir); 511 iterator = Collections.emptyIterator(); 512 } 513 514 /** 515 * Returns the name of the next non-directory node or {@code null} if 516 * there are no remaining nodes to visit. 517 */ 518 private String next() throws IOException { 519 for (;;) { 520 while (iterator.hasNext()) { 521 String name = iterator.next(); 522 ImageReader.Node node = SystemImage.reader().findNode(name); 523 if (node.isDirectory()) { 524 stack.push(node); 525 } else { 526 // strip /modules/$MODULE/ prefix 527 return name.substring(moduleRoot.length() + 1); 528 } 529 } 530 531 if (stack.isEmpty()) { 532 return null; 533 } else { 534 ImageReader.Node dir = stack.poll(); 535 assert dir.isDirectory(); 536 iterator = dir.getChildNames().iterator(); 537 } 538 } 539 } 540 541 @Override 542 public boolean tryAdvance(Consumer<? super String> action) { 543 String next; 544 try { 545 next = next(); 546 } catch (IOException ioe) { 547 throw new UncheckedIOException(ioe); 548 } 549 if (next != null) { 550 action.accept(next); 551 return true; 552 } else { 553 return false; 554 } 555 } 556 557 @Override 558 public Spliterator<String> trySplit() { 559 return null; 560 } 561 562 @Override 563 public int characteristics() { 564 return Spliterator.DISTINCT + Spliterator.NONNULL + Spliterator.IMMUTABLE; 565 } 566 567 @Override 568 public long estimateSize() { 569 return Long.MAX_VALUE; 570 } 571 } 572 }