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