1 /*
  2  * Copyright (c) 2015, 2024, 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 
 26 package jdk.internal.module;
 27 
 28 import java.io.File;
 29 import java.io.IOError;
 30 import java.io.IOException;
 31 import java.io.InputStream;
 32 import java.io.UncheckedIOException;
 33 import java.lang.module.ModuleReader;
 34 import java.lang.module.ModuleReference;
 35 import java.net.URI;
 36 import java.nio.ByteBuffer;
 37 import java.nio.file.Files;
 38 import java.nio.file.Path;
 39 import java.util.List;
 40 import java.util.Objects;
 41 import java.util.Optional;
 42 import java.util.concurrent.locks.Lock;
 43 import java.util.concurrent.locks.ReadWriteLock;
 44 import java.util.concurrent.locks.ReentrantReadWriteLock;
 45 import java.util.function.Supplier;
 46 import java.util.jar.JarEntry;
 47 import java.util.jar.JarFile;
 48 import java.util.stream.Stream;
 49 import java.util.zip.ZipFile;
 50 
 51 import jdk.internal.jmod.JmodFile;
 52 import jdk.internal.module.ModuleHashes.HashSupplier;
 53 import sun.net.www.ParseUtil;
 54 
 55 
 56 /**
 57  * A factory for creating ModuleReference implementations where the modules are
 58  * packaged as modular JAR file, JMOD files or where the modules are exploded
 59  * on the file system.
 60  */
 61 
 62 class ModuleReferences {
 63     private ModuleReferences() { }
 64 
 65     /**
 66      * Creates a ModuleReference to a possibly-patched module
 67      */
 68     private static ModuleReference newModule(ModuleInfo.Attributes attrs,
 69                                              URI uri,
 70                                              Supplier<ModuleReader> supplier,
 71                                              ModulePatcher patcher,
 72                                              HashSupplier hasher) {
 73         ModuleReference mref = new ModuleReferenceImpl(attrs.descriptor(),
 74                                                        uri,
 75                                                        supplier,
 76                                                        null,
 77                                                        attrs.target(),
 78                                                        attrs.recordedHashes(),
 79                                                        hasher,
 80                                                        attrs.moduleResolution());
 81         if (patcher != null)
 82             mref = patcher.patchIfNeeded(mref);
 83 
 84         return mref;
 85     }
 86 
 87     /**
 88      * Creates a ModuleReference to a possibly-patched module in a modular JAR.
 89      */
 90     static ModuleReference newJarModule(ModuleInfo.Attributes attrs,
 91                                         ModulePatcher patcher,
 92                                         Path file) {
 93         URI uri = file.toUri();
 94         String fileString = file.toString();
 95         Supplier<ModuleReader> supplier = new Supplier<>() {
 96             @Override
 97             public ModuleReader get() {
 98                 return new JarModuleReader(fileString, uri);
 99             }
100         };
101         HashSupplier hasher = new HashSupplier() {
102             @Override
103             public byte[] generate(String algorithm) {
104               return ModuleHashes.computeHash(supplier, algorithm);
105             }
106         };
107         return newModule(attrs, uri, supplier, patcher, hasher);
108     }
109 
110     /**
111      * Creates a ModuleReference to a module in a JMOD file.
112      */
113     static ModuleReference newJModModule(ModuleInfo.Attributes attrs, Path file) {
114         URI uri = file.toUri();
115         Supplier<ModuleReader> supplier = () -> new JModModuleReader(file, uri);
116         HashSupplier hasher = (a) -> ModuleHashes.computeHash(supplier, a);
117         return newModule(attrs, uri, supplier, null, hasher);
118     }
119 
120     /**
121      * Creates a ModuleReference to a possibly-patched exploded module.
122      */
123     static ModuleReference newExplodedModule(ModuleInfo.Attributes attrs,
124                                              ModulePatcher patcher,
125                                              Path dir) {
126         Supplier<ModuleReader> supplier = () -> new ExplodedModuleReader(dir);
127         return newModule(attrs, dir.toUri(), supplier, patcher, null);
128     }
129 
130 
131     /**
132      * A base module reader that encapsulates machinery required to close the
133      * module reader safely.
134      */
135     abstract static class SafeCloseModuleReader implements ModuleReader {
136 
137         // RW lock to support safe close
138         private final ReadWriteLock lock = new ReentrantReadWriteLock();
139         private final Lock readLock = lock.readLock();
140         private final Lock writeLock = lock.writeLock();
141         private boolean closed;
142 
143         SafeCloseModuleReader() { }
144 
145         /**
146          * Returns a URL to  resource. This method is invoked by the find
147          * method to do the actual work of finding the resource.
148          */
149         abstract Optional<URI> implFind(String name) throws IOException;
150 
151         /**
152          * Returns an input stream for reading a resource. This method is
153          * invoked by the open method to do the actual work of opening
154          * an input stream to the resource.
155          */
156         abstract Optional<InputStream> implOpen(String name) throws IOException;
157 
158         /**
159          * Returns a stream of the names of resources in the module. This
160          * method is invoked by the list method to do the actual work of
161          * creating the stream.
162          */
163         abstract Stream<String> implList() throws IOException;
164 
165         /**
166          * Closes the module reader. This method is invoked by close to do the
167          * actual work of closing the module reader.
168          */
169         abstract void implClose() throws IOException;
170 
171         @Override
172         public final Optional<URI> find(String name) throws IOException {
173             readLock.lock();
174             try {
175                 if (!closed) {
176                     return implFind(name);
177                 } else {
178                     throw new IOException("ModuleReader is closed");
179                 }
180             } finally {
181                 readLock.unlock();
182             }
183         }
184 
185 
186         @Override
187         public final Optional<InputStream> open(String name) throws IOException {
188             readLock.lock();
189             try {
190                 if (!closed) {
191                     return implOpen(name);
192                 } else {
193                     throw new IOException("ModuleReader is closed");
194                 }
195             } finally {
196                 readLock.unlock();
197             }
198         }
199 
200         @Override
201         public final Stream<String> list() throws IOException {
202             readLock.lock();
203             try {
204                 if (!closed) {
205                     return implList();
206                 } else {
207                     throw new IOException("ModuleReader is closed");
208                 }
209             } finally {
210                 readLock.unlock();
211             }
212         }
213 
214         @Override
215         public final void close() throws IOException {
216             writeLock.lock();
217             try {
218                 if (!closed) {
219                     closed = true;
220                     implClose();
221                 }
222             } finally {
223                 writeLock.unlock();
224             }
225         }
226     }
227 
228 
229     /**
230      * A ModuleReader for a modular JAR file.
231      */
232     static class JarModuleReader extends SafeCloseModuleReader {
233         private final JarFile jf;
234         private final URI uri;
235 
236         static JarFile newJarFile(String path) {
237             try {
238                 return new JarFile(new File(path),
239                                    true,                       // verify
240                                    ZipFile.OPEN_READ,
241                                    JarFile.runtimeVersion());
242             } catch (IOException ioe) {
243                 throw new UncheckedIOException(ioe);
244             }
245         }
246 
247         JarModuleReader(String path, URI uri) {
248             this.jf = newJarFile(path);
249             this.uri = uri;
250         }
251 
252         private JarEntry getEntry(String name) {
253             return jf.getJarEntry(Objects.requireNonNull(name));
254         }
255 
256         @Override
257         Optional<URI> implFind(String name) throws IOException {
258             JarEntry je = getEntry(name);
259             if (je != null) {
260                 if (jf.isMultiRelease())
261                     name = je.getRealName();
262                 if (je.isDirectory() && !name.endsWith("/"))
263                     name += "/";
264                 String encodedPath = ParseUtil.encodePath(name, false);
265                 String uris = "jar:" + uri + "!/" + encodedPath;
266                 return Optional.of(URI.create(uris));
267             } else {
268                 return Optional.empty();
269             }
270         }
271 
272         @Override
273         Optional<InputStream> implOpen(String name) throws IOException {
274             JarEntry je = getEntry(name);
275             if (je != null) {
276                 return Optional.of(jf.getInputStream(je));
277             } else {
278                 return Optional.empty();
279             }
280         }
281 
282         @Override
283         Stream<String> implList() throws IOException {
284             // take snapshot to avoid async close
285             List<String> names = jf.versionedStream()
286                     .map(JarEntry::getName)
287                     .toList();
288             return names.stream();
289         }
290 
291         @Override
292         void implClose() throws IOException {
293             jf.close();
294         }
295     }
296 
297 
298     /**
299      * A ModuleReader for a JMOD file.
300      */
301     static class JModModuleReader extends SafeCloseModuleReader {
302         private final JmodFile jf;
303         private final URI uri;
304 
305         static JmodFile newJmodFile(Path path) {
306             try {
307                 return new JmodFile(path);
308             } catch (IOException ioe) {
309                 throw new UncheckedIOException(ioe);
310             }
311         }
312 
313         JModModuleReader(Path path, URI uri) {
314             this.jf = newJmodFile(path);
315             this.uri = uri;
316         }
317 
318         private JmodFile.Entry getEntry(String name) {
319             Objects.requireNonNull(name);
320             return jf.getEntry(JmodFile.Section.CLASSES, name);
321         }
322 
323         @Override
324         Optional<URI> implFind(String name) {
325             JmodFile.Entry je = getEntry(name);
326             if (je != null) {
327                 if (je.isDirectory() && !name.endsWith("/"))
328                     name += "/";
329                 String encodedPath = ParseUtil.encodePath(name, false);
330                 String uris = "jmod:" + uri + "!/" + encodedPath;
331                 return Optional.of(URI.create(uris));
332             } else {
333                 return Optional.empty();
334             }
335         }
336 
337         @Override
338         Optional<InputStream> implOpen(String name) throws IOException {
339             JmodFile.Entry je = getEntry(name);
340             if (je != null) {
341                 return Optional.of(jf.getInputStream(je));
342             } else {
343                 return Optional.empty();
344             }
345         }
346 
347         @Override
348         Stream<String> implList() throws IOException {
349             // take snapshot to avoid async close
350             List<String> names = jf.stream()
351                     .filter(e -> e.section() == JmodFile.Section.CLASSES)
352                     .map(JmodFile.Entry::name)
353                     .toList();
354             return names.stream();
355         }
356 
357         @Override
358         void implClose() throws IOException {
359             jf.close();
360         }
361     }
362 
363 
364     /**
365      * A ModuleReader for an exploded module.
366      */
367     static class ExplodedModuleReader implements ModuleReader {
368         private final Path dir;
369         private volatile boolean closed;
370 
371         ExplodedModuleReader(Path dir) {
372             this.dir = dir;
373         }
374 
375         /**
376          * Throws IOException if the module reader is closed;
377          */
378         private void ensureOpen() throws IOException {
379             if (closed) throw new IOException("ModuleReader is closed");
380         }
381 
382         @Override
383         public Optional<URI> find(String name) throws IOException {
384             ensureOpen();
385             Path path = Resources.toFilePath(dir, name);
386             if (path != null) {
387                 try {
388                     return Optional.of(path.toUri());
389                 } catch (IOError e) {
390                     throw (IOException) e.getCause();
391                 }
392             } else {
393                 return Optional.empty();
394             }
395         }
396 
397         @Override
398         public Optional<InputStream> open(String name) throws IOException {
399             ensureOpen();
400             Path path = Resources.toFilePath(dir, name);
401             if (path != null) {
402                 return Optional.of(Files.newInputStream(path));
403             } else {
404                 return Optional.empty();
405             }
406         }
407 
408         @Override
409         public Optional<ByteBuffer> read(String name) throws IOException {
410             ensureOpen();
411             Path path = Resources.toFilePath(dir, name);
412             if (path != null) {
413                 return Optional.of(ByteBuffer.wrap(Files.readAllBytes(path)));
414             } else {
415                 return Optional.empty();
416             }
417         }
418 
419         @Override
420         public Stream<String> list() throws IOException {
421             ensureOpen();
422             return Files.walk(dir, Integer.MAX_VALUE)
423                         .map(f -> Resources.toResourceName(dir, f))
424                         .filter(s -> s.length() > 0);
425         }
426 
427         @Override
428         public void close() {
429             closed = true;
430         }
431     }
432 
433 }