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