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