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,
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
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 }
|
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
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.LinkedHashSet;
40 import java.util.List;
41 import java.util.Objects;
42 import java.util.Optional;
43 import java.util.Set;
44 import java.util.concurrent.locks.Lock;
45 import java.util.concurrent.locks.ReadWriteLock;
46 import java.util.concurrent.locks.ReentrantReadWriteLock;
47 import java.util.function.Predicate;
48 import java.util.function.Supplier;
49 import java.util.jar.JarEntry;
50 import java.util.jar.JarFile;
51 import java.util.stream.Stream;
52 import java.util.zip.ZipFile;
53
54 import jdk.internal.jmod.JmodFile;
55 import jdk.internal.module.ModuleHashes.HashSupplier;
56 import sun.net.www.ParseUtil;
57
58 /**
59 * A factory for creating ModuleReference implementations where the modules are
60 * packaged as modular JAR file, JMOD files or where the modules are exploded
61 * on the file system.
62 */
63
64 class ModuleReferences {
65 private ModuleReferences() { }
66
67 /**
68 * Creates a ModuleReference to a possibly-patched module
69 */
70 private static ModuleReference newModule(ModuleInfo.Attributes attrs,
71 URI uri,
72 Supplier<ModuleReader> supplier,
73 ModulePatcher patcher,
74 HashSupplier hasher) {
75 ModuleReference mref = new ModuleReferenceImpl(attrs.descriptor(),
76 uri,
77 supplier,
107 }
108 };
109 return newModule(attrs, uri, supplier, patcher, hasher);
110 }
111
112 /**
113 * Creates a ModuleReference to a module in a JMOD file.
114 */
115 static ModuleReference newJModModule(ModuleInfo.Attributes attrs, Path file) {
116 URI uri = file.toUri();
117 Supplier<ModuleReader> supplier = () -> new JModModuleReader(file, uri);
118 HashSupplier hasher = (a) -> ModuleHashes.computeHash(supplier, a);
119 return newModule(attrs, uri, supplier, null, hasher);
120 }
121
122 /**
123 * Creates a ModuleReference to a possibly-patched exploded module.
124 */
125 static ModuleReference newExplodedModule(ModuleInfo.Attributes attrs,
126 ModulePatcher patcher,
127 boolean previewMode,
128 Path dir) {
129 Supplier<ModuleReader> supplier = () -> new ExplodedModuleReader(dir, previewMode);
130 return newModule(attrs, dir.toUri(), supplier, patcher, null);
131 }
132
133
134 /**
135 * A base module reader that encapsulates machinery required to close the
136 * module reader safely.
137 */
138 abstract static class SafeCloseModuleReader implements ModuleReader {
139
140 // RW lock to support safe close
141 private final ReadWriteLock lock = new ReentrantReadWriteLock();
142 private final Lock readLock = lock.readLock();
143 private final Lock writeLock = lock.writeLock();
144 private boolean closed;
145
146 SafeCloseModuleReader() { }
147
148 /**
149 * Returns a URL to resource. This method is invoked by the find
351 Stream<String> implList() throws IOException {
352 // take snapshot to avoid async close
353 List<String> names = jf.stream()
354 .filter(e -> e.section() == JmodFile.Section.CLASSES)
355 .map(JmodFile.Entry::name)
356 .toList();
357 return names.stream();
358 }
359
360 @Override
361 void implClose() throws IOException {
362 jf.close();
363 }
364 }
365
366
367 /**
368 * A ModuleReader for an exploded module.
369 */
370 static class ExplodedModuleReader implements ModuleReader {
371 private static final String PREVIEW_PREFIX = "META-INF/preview";
372
373 private final Path dir;
374 private final Path previewDir;
375 private volatile boolean closed;
376
377 ExplodedModuleReader(Path dir, boolean previewMode) {
378 this.dir = dir;
379 Path path = dir.resolve("META-INF", "preview");
380 this.previewDir = (previewMode && Files.isDirectory(path)) ? path : null;
381 }
382
383 /**
384 * Throws IOException if the module reader is closed.
385 */
386 private void ensureOpen() throws IOException {
387 if (closed) throw new IOException("ModuleReader is closed");
388 }
389
390 /**
391 * Returns a file path to a resource in the module or null if not found.
392 */
393 private Path toFilePath(String name) throws IOException {
394 if (previewDir != null) {
395 if (isPreviewEntry(name)) {
396 return null;
397 }
398 Path previewPath = Resources.toFilePath(previewDir, name);
399 if (previewPath != null) {
400 return previewPath;
401 }
402 }
403 return Resources.toFilePath(dir, name);
404 }
405
406 @Override
407 public Optional<URI> find(String name) throws IOException {
408 ensureOpen();
409 Path path = toFilePath(name);
410 if (path != null) {
411 try {
412 return Optional.of(path.toUri());
413 } catch (IOError e) {
414 throw (IOException) e.getCause();
415 }
416 } else {
417 return Optional.empty();
418 }
419 }
420
421 @Override
422 public Optional<InputStream> open(String name) throws IOException {
423 ensureOpen();
424 Path path = toFilePath(name);
425 if (path != null) {
426 return Optional.of(Files.newInputStream(path));
427 } else {
428 return Optional.empty();
429 }
430 }
431
432 @Override
433 public Optional<ByteBuffer> read(String name) throws IOException {
434 ensureOpen();
435 Path path = toFilePath(name);
436 if (path != null) {
437 return Optional.of(ByteBuffer.wrap(Files.readAllBytes(path)));
438 } else {
439 return Optional.empty();
440 }
441 }
442
443 @Override
444 public Stream<String> list() throws IOException {
445 ensureOpen();
446
447 // not an exploded image, preview features not enabled, or no META-INF/preview
448 if (previewDir == null) {
449 return Files.walk(dir, Integer.MAX_VALUE)
450 .skip(1) // skip root
451 .map(f -> Resources.toResourceName(dir, f));
452 }
453
454 // combine resources from file tree with resources from META-INF/preview
455 var names = new LinkedHashSet<String>();
456 walkAndCollect(dir, rn -> !isPreviewEntry(rn), names);
457 walkAndCollect(previewDir, _ -> true, names);
458 return names.stream();
459 }
460
461 @Override
462 public void close() {
463 closed = true;
464 }
465
466 /**
467 * Adds the names of resources in the given tree to a collection if the names
468 * are matched by a given predicate.
469 */
470 private static void walkAndCollect(Path root,
471 Predicate<String> matcher,
472 Set<String> names) throws IOException {
473 try (Stream<Path> files = Files.walk(root, Integer.MAX_VALUE)) {
474 files.skip(1) // skip root
475 .map(f -> Resources.toResourceName(root, f))
476 .filter(matcher)
477 .forEach(names::add);
478 }
479 }
480
481 /**
482 * Returns true if a resource is in the META-INF/preview tree.
483 */
484 private static boolean isPreviewEntry(String name) {
485 return name.startsWith(PREVIEW_PREFIX) &&
486 (name.length() == PREVIEW_PREFIX.length()
487 || name.charAt(PREVIEW_PREFIX.length()) == '/');
488 }
489 }
490 }
|