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