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 }