|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package jdk.internal.module; |
|
|
|
import java.io.File; |
|
import java.io.IOError; |
|
import java.io.IOException; |
|
import java.io.InputStream; |
|
import java.io.UncheckedIOException; |
|
import java.lang.module.ModuleReader; |
|
import java.lang.module.ModuleReference; |
|
import java.net.URI; |
|
import java.nio.ByteBuffer; |
|
import java.nio.file.Files; |
|
import java.nio.file.Path; |
|
import java.util.List; |
|
import java.util.Objects; |
|
import java.util.Optional; |
|
import java.util.concurrent.locks.Lock; |
|
import java.util.concurrent.locks.ReadWriteLock; |
|
import java.util.concurrent.locks.ReentrantReadWriteLock; |
|
import java.util.function.Supplier; |
|
import java.util.jar.JarEntry; |
|
import java.util.jar.JarFile; |
|
import java.util.stream.Stream; |
|
import java.util.zip.ZipFile; |
|
|
|
import jdk.internal.jmod.JmodFile; |
|
import jdk.internal.module.ModuleHashes.HashSupplier; |
|
import sun.net.www.ParseUtil; |
|
|
|
|
|
/** |
|
* A factory for creating ModuleReference implementations where the modules are |
|
* packaged as modular JAR file, JMOD files or where the modules are exploded |
|
* on the file system. |
|
*/ |
|
|
|
class ModuleReferences { |
|
private ModuleReferences() { } |
|
|
|
|
|
|
|
*/ |
|
private static ModuleReference newModule(ModuleInfo.Attributes attrs, |
|
URI uri, |
|
Supplier<ModuleReader> supplier, |
|
ModulePatcher patcher, |
|
HashSupplier hasher) { |
|
ModuleReference mref = new ModuleReferenceImpl(attrs.descriptor(), |
|
uri, |
|
supplier, |
|
null, |
|
attrs.target(), |
|
attrs.recordedHashes(), |
|
hasher, |
|
attrs.moduleResolution()); |
|
if (patcher != null) |
|
mref = patcher.patchIfNeeded(mref); |
|
|
|
return mref; |
|
} |
|
|
|
|
|
|
|
*/ |
|
static ModuleReference newJarModule(ModuleInfo.Attributes attrs, |
|
ModulePatcher patcher, |
|
Path file) { |
|
URI uri = file.toUri(); |
|
Supplier<ModuleReader> supplier = () -> new JarModuleReader(file, uri); |
|
HashSupplier hasher = (a) -> ModuleHashes.computeHash(supplier, a); |
|
return newModule(attrs, uri, supplier, patcher, hasher); |
|
} |
|
|
|
|
|
|
|
*/ |
|
static ModuleReference newJModModule(ModuleInfo.Attributes attrs, Path file) { |
|
URI uri = file.toUri(); |
|
Supplier<ModuleReader> supplier = () -> new JModModuleReader(file, uri); |
|
HashSupplier hasher = (a) -> ModuleHashes.computeHash(supplier, a); |
|
return newModule(attrs, uri, supplier, null, hasher); |
|
} |
|
|
|
|
|
|
|
*/ |
|
static ModuleReference newExplodedModule(ModuleInfo.Attributes attrs, |
|
ModulePatcher patcher, |
|
Path dir) { |
|
Supplier<ModuleReader> supplier = () -> new ExplodedModuleReader(dir); |
|
return newModule(attrs, dir.toUri(), supplier, patcher, null); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static abstract class SafeCloseModuleReader implements ModuleReader { |
|
|
|
|
|
private final ReadWriteLock lock = new ReentrantReadWriteLock(); |
|
private final Lock readLock = lock.readLock(); |
|
private final Lock writeLock = lock.writeLock(); |
|
private boolean closed; |
|
|
|
SafeCloseModuleReader() { } |
|
|
|
|
|
|
|
|
|
*/ |
|
abstract Optional<URI> implFind(String name) throws IOException; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
abstract Optional<InputStream> implOpen(String name) throws IOException; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
abstract Stream<String> implList() throws IOException; |
|
|
|
|
|
|
|
|
|
*/ |
|
abstract void implClose() throws IOException; |
|
|
|
@Override |
|
public final Optional<URI> find(String name) throws IOException { |
|
readLock.lock(); |
|
try { |
|
if (!closed) { |
|
return implFind(name); |
|
} else { |
|
throw new IOException("ModuleReader is closed"); |
|
} |
|
} finally { |
|
readLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
@Override |
|
public final Optional<InputStream> open(String name) throws IOException { |
|
readLock.lock(); |
|
try { |
|
if (!closed) { |
|
return implOpen(name); |
|
} else { |
|
throw new IOException("ModuleReader is closed"); |
|
} |
|
} finally { |
|
readLock.unlock(); |
|
} |
|
} |
|
|
|
@Override |
|
public final Stream<String> list() throws IOException { |
|
readLock.lock(); |
|
try { |
|
if (!closed) { |
|
return implList(); |
|
} else { |
|
throw new IOException("ModuleReader is closed"); |
|
} |
|
} finally { |
|
readLock.unlock(); |
|
} |
|
} |
|
|
|
@Override |
|
public final void close() throws IOException { |
|
writeLock.lock(); |
|
try { |
|
if (!closed) { |
|
closed = true; |
|
implClose(); |
|
} |
|
} finally { |
|
writeLock.unlock(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
static class JarModuleReader extends SafeCloseModuleReader { |
|
private final JarFile jf; |
|
private final URI uri; |
|
|
|
static JarFile newJarFile(Path path) { |
|
try { |
|
return new JarFile(new File(path.toString()), |
|
true, |
|
ZipFile.OPEN_READ, |
|
JarFile.runtimeVersion()); |
|
} catch (IOException ioe) { |
|
throw new UncheckedIOException(ioe); |
|
} |
|
} |
|
|
|
JarModuleReader(Path path, URI uri) { |
|
this.jf = newJarFile(path); |
|
this.uri = uri; |
|
} |
|
|
|
private JarEntry getEntry(String name) { |
|
return jf.getJarEntry(Objects.requireNonNull(name)); |
|
} |
|
|
|
@Override |
|
Optional<URI> implFind(String name) throws IOException { |
|
JarEntry je = getEntry(name); |
|
if (je != null) { |
|
if (jf.isMultiRelease()) |
|
name = je.getRealName(); |
|
if (je.isDirectory() && !name.endsWith("/")) |
|
name += "/"; |
|
String encodedPath = ParseUtil.encodePath(name, false); |
|
String uris = "jar:" + uri + "!/" + encodedPath; |
|
return Optional.of(URI.create(uris)); |
|
} else { |
|
return Optional.empty(); |
|
} |
|
} |
|
|
|
@Override |
|
Optional<InputStream> implOpen(String name) throws IOException { |
|
JarEntry je = getEntry(name); |
|
if (je != null) { |
|
return Optional.of(jf.getInputStream(je)); |
|
} else { |
|
return Optional.empty(); |
|
} |
|
} |
|
|
|
@Override |
|
Stream<String> implList() throws IOException { |
|
|
|
List<String> names = jf.versionedStream() |
|
.map(JarEntry::getName) |
|
.toList(); |
|
return names.stream(); |
|
} |
|
|
|
@Override |
|
void implClose() throws IOException { |
|
jf.close(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
static class JModModuleReader extends SafeCloseModuleReader { |
|
private final JmodFile jf; |
|
private final URI uri; |
|
|
|
static JmodFile newJmodFile(Path path) { |
|
try { |
|
return new JmodFile(path); |
|
} catch (IOException ioe) { |
|
throw new UncheckedIOException(ioe); |
|
} |
|
} |
|
|
|
JModModuleReader(Path path, URI uri) { |
|
this.jf = newJmodFile(path); |
|
this.uri = uri; |
|
} |
|
|
|
private JmodFile.Entry getEntry(String name) { |
|
Objects.requireNonNull(name); |
|
return jf.getEntry(JmodFile.Section.CLASSES, name); |
|
} |
|
|
|
@Override |
|
Optional<URI> implFind(String name) { |
|
JmodFile.Entry je = getEntry(name); |
|
if (je != null) { |
|
if (je.isDirectory() && !name.endsWith("/")) |
|
name += "/"; |
|
String encodedPath = ParseUtil.encodePath(name, false); |
|
String uris = "jmod:" + uri + "!/" + encodedPath; |
|
return Optional.of(URI.create(uris)); |
|
} else { |
|
return Optional.empty(); |
|
} |
|
} |
|
|
|
@Override |
|
Optional<InputStream> implOpen(String name) throws IOException { |
|
JmodFile.Entry je = getEntry(name); |
|
if (je != null) { |
|
return Optional.of(jf.getInputStream(je)); |
|
} else { |
|
return Optional.empty(); |
|
} |
|
} |
|
|
|
@Override |
|
Stream<String> implList() throws IOException { |
|
|
|
List<String> names = jf.stream() |
|
.filter(e -> e.section() == JmodFile.Section.CLASSES) |
|
.map(JmodFile.Entry::name) |
|
.toList(); |
|
return names.stream(); |
|
} |
|
|
|
@Override |
|
void implClose() throws IOException { |
|
jf.close(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
static class ExplodedModuleReader implements ModuleReader { |
|
private final Path dir; |
|
private volatile boolean closed; |
|
|
|
ExplodedModuleReader(Path dir) { |
|
this.dir = dir; |
|
|
|
// when running with a security manager then check that the caller |
|
|
|
@SuppressWarnings("removal") |
|
SecurityManager sm = System.getSecurityManager(); |
|
if (sm != null) { |
|
boolean unused = Files.isDirectory(dir); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private void ensureOpen() throws IOException { |
|
if (closed) throw new IOException("ModuleReader is closed"); |
|
} |
|
|
|
@Override |
|
public Optional<URI> find(String name) throws IOException { |
|
ensureOpen(); |
|
Path path = Resources.toFilePath(dir, name); |
|
if (path != null) { |
|
try { |
|
return Optional.of(path.toUri()); |
|
} catch (IOError e) { |
|
throw (IOException) e.getCause(); |
|
} |
|
} else { |
|
return Optional.empty(); |
|
} |
|
} |
|
|
|
@Override |
|
public Optional<InputStream> open(String name) throws IOException { |
|
ensureOpen(); |
|
Path path = Resources.toFilePath(dir, name); |
|
if (path != null) { |
|
return Optional.of(Files.newInputStream(path)); |
|
} else { |
|
return Optional.empty(); |
|
} |
|
} |
|
|
|
@Override |
|
public Optional<ByteBuffer> read(String name) throws IOException { |
|
ensureOpen(); |
|
Path path = Resources.toFilePath(dir, name); |
|
if (path != null) { |
|
return Optional.of(ByteBuffer.wrap(Files.readAllBytes(path))); |
|
} else { |
|
return Optional.empty(); |
|
} |
|
} |
|
|
|
@Override |
|
public Stream<String> list() throws IOException { |
|
ensureOpen(); |
|
return Files.walk(dir, Integer.MAX_VALUE) |
|
.map(f -> Resources.toResourceName(dir, f)) |
|
.filter(s -> s.length() > 0); |
|
} |
|
|
|
@Override |
|
public void close() { |
|
closed = true; |
|
} |
|
} |
|
|
|
} |