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 resourcePath) throws IOException { 418 Objects.requireNonNull(resourcePath); 419 if (closed) 420 throw new IOException("ModuleReader is closed"); 421 ImageReader imageReader = SystemImage.reader(); 422 if (imageReader != null) { 423 ImageReader.Node node = imageReader.findNode("/modules" + resourcePath); 424 return node != null && node.isResource(); 425 } else { 426 // not an images build 427 return false; 428 } 429 } 430 431 @Override 432 public Optional<URI> find(String name) throws IOException { 433 String resourcePath = "/" + module + "/" + name; 434 if (containsResource(resourcePath)) { 435 URI u = JNUA.create("jrt", resourcePath); 436 return Optional.of(u); 437 } else { 438 return Optional.empty(); 439 } 440 } 441 442 @Override 443 public Optional<InputStream> open(String name) throws IOException { 444 return read(name).map(this::toInputStream); 445 } 446 447 private InputStream toInputStream(ByteBuffer bb) { // ## -> ByteBuffer? 448 try { 449 int rem = bb.remaining(); 450 byte[] bytes = new byte[rem]; 451 bb.get(bytes); 452 return new ByteArrayInputStream(bytes); 453 } finally { 454 release(bb); 455 } 456 } 457 458 /** 459 * Returns the node for the given resource if found. If the name references 460 * a non-resource node, then {@code null} is returned. 461 */ 462 private ImageReader.Node findResource(ImageReader reader, String name) throws IOException { 463 Objects.requireNonNull(name); 464 if (closed) { 465 throw new IOException("ModuleReader is closed"); 466 } 467 String nodeName = "/modules/" + module + "/" + name; 468 ImageReader.Node node = reader.findNode(nodeName); 469 return (node != null && node.isResource()) ? node : null; 470 } 471 472 @Override 473 public Optional<ByteBuffer> read(String name) throws IOException { 474 ImageReader reader = SystemImage.reader(); 475 return Optional.ofNullable(findResource(reader, name)) 476 .map(reader::getResourceBuffer); 477 } 478 479 @Override 480 public void release(ByteBuffer bb) { 481 Objects.requireNonNull(bb); 482 ImageReader.releaseByteBuffer(bb); 483 } 484 485 @Override 486 public Stream<String> list() throws IOException { 487 if (closed) 488 throw new IOException("ModuleReader is closed"); 489 490 Spliterator<String> s = new ModuleContentSpliterator(module); 491 return StreamSupport.stream(s, false); 492 } 493 494 @Override 495 public void close() { 496 // nothing else to do 497 closed = true; 498 } 499 } 500 501 /** 502 * A Spliterator for traversing the resources of a module linked into the 503 * run-time image. 504 */ 505 private static class ModuleContentSpliterator implements Spliterator<String> { 506 final String moduleRoot; 507 final Deque<ImageReader.Node> stack; 508 Iterator<String> iterator; 509 510 ModuleContentSpliterator(String module) throws IOException { 511 moduleRoot = "/modules/" + module; 512 stack = new ArrayDeque<>(); 513 514 // push the root node to the stack to get started 515 ImageReader.Node dir = SystemImage.reader().findNode(moduleRoot); 516 if (dir == null || !dir.isDirectory()) 517 throw new IOException(moduleRoot + " not a directory"); 518 stack.push(dir); 519 iterator = Collections.emptyIterator(); 520 } 521 522 /** 523 * Returns the name of the next non-directory node or {@code null} if 524 * there are no remaining nodes to visit. 525 */ 526 private String next() throws IOException { 527 for (;;) { 528 while (iterator.hasNext()) { 529 String name = iterator.next(); 530 ImageReader.Node node = SystemImage.reader().findNode(name); 531 if (node.isDirectory()) { 532 stack.push(node); 533 } else { 534 // strip /modules/$MODULE/ prefix 535 return name.substring(moduleRoot.length() + 1); 536 } 537 } 538 539 if (stack.isEmpty()) { 540 return null; 541 } else { 542 ImageReader.Node dir = stack.poll(); 543 assert dir.isDirectory(); 544 iterator = dir.getChildNames().iterator(); 545 } 546 } 547 } 548 549 @Override 550 public boolean tryAdvance(Consumer<? super String> action) { 551 String next; 552 try { 553 next = next(); 554 } catch (IOException ioe) { 555 throw new UncheckedIOException(ioe); 556 } 557 if (next != null) { 558 action.accept(next); 559 return true; 560 } else { 561 return false; 562 } 563 } 564 565 @Override 566 public Spliterator<String> trySplit() { 567 return null; 568 } 569 570 @Override 571 public int characteristics() { 572 return Spliterator.DISTINCT + Spliterator.NONNULL + Spliterator.IMMUTABLE; 573 } 574 575 @Override 576 public long estimateSize() { 577 return Long.MAX_VALUE; 578 } 579 } 580 }