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 }