8168789: ModuleReader.list and ModuleFinder.of update
Reviewed-by: mchung
This commit is contained in:
parent
d0891a2ffc
commit
83df093985
@ -31,6 +31,7 @@ import java.io.PrintStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@ -1997,6 +1998,13 @@ public class ModuleDescriptor
|
||||
public Optional<ModuleHashes> hashes(ModuleDescriptor descriptor) {
|
||||
return descriptor.hashes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleFinder newModulePath(Runtime.Version version,
|
||||
boolean isLinkPhase,
|
||||
Path... entries) {
|
||||
return new ModulePath(version, isLinkPhase, entries);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -228,7 +228,7 @@ public interface ModuleFinder {
|
||||
*
|
||||
* <li><p> If the name matches the regular expression {@code
|
||||
* "-(\\d+(\\.|$))"} then the module name will be derived from the
|
||||
* subsequence proceeding the hyphen of the first occurrence. The
|
||||
* subsequence preceding the hyphen of the first occurrence. The
|
||||
* subsequence after the hyphen is parsed as a {@link
|
||||
* ModuleDescriptor.Version} and ignored if it cannot be parsed as
|
||||
* a {@code Version}. </p></li>
|
||||
@ -248,18 +248,23 @@ public interface ModuleFinder {
|
||||
* <li><p> It {@link ModuleDescriptor#requires() requires} {@code
|
||||
* java.base}. </p></li>
|
||||
*
|
||||
* <li><p> All entries in the JAR file with names ending with {@code
|
||||
* .class} are assumed to be class files where the name corresponds
|
||||
* to the fully qualified name of the class. The packages of all
|
||||
* classes are {@link ModuleDescriptor#exports() exported}. </p></li>
|
||||
* <li><p> The set of packages in the module is derived from the names
|
||||
* of non-directory entries in the JAR file. A candidate package name
|
||||
* is derived from an entry using the characters up to, but not
|
||||
* including, the last forward slash. All remaining forward slashes are
|
||||
* replaced with dot ({@code "."}). If the resulting string is a valid
|
||||
* Java identifier then it is assumed to be a package name. For example,
|
||||
* if the JAR file contains an entry "{@code p/q/Foo.class}" then the
|
||||
* package name derived is "{@code p.q}". All packages are {@link
|
||||
* ModuleDescriptor#exports() exported}. </p></li>
|
||||
*
|
||||
* <li><p> The contents of all entries starting with {@code
|
||||
* <li><p> The contents of entries starting with {@code
|
||||
* META-INF/services/} are assumed to be service configuration files
|
||||
* (see {@link java.util.ServiceLoader}). The name of the file
|
||||
* (that follows {@code META-INF/services/}) is assumed to be the
|
||||
* fully-qualified binary name of a service type. The entries in the
|
||||
* file are assumed to be the fully-qualified binary names of
|
||||
* provider classes. </p></li>
|
||||
* (see {@link java.util.ServiceLoader}). If the name of a file
|
||||
* (that follows {@code META-INF/services/}) is a legal Java identifier
|
||||
* then it is assumed to be the fully-qualified binary name of a
|
||||
* service type. The entries in the file are assumed to be the
|
||||
* fully-qualified binary names of provider classes. </p></li>
|
||||
*
|
||||
* <li><p> If the JAR file has a {@code Main-Class} attribute in its
|
||||
* main manifest then its value is the {@link
|
||||
@ -271,8 +276,8 @@ public interface ModuleFinder {
|
||||
* {@link ModuleDescriptor.Builder ModuleDescriptor.Builder} API) for an
|
||||
* automatic module then {@code FindException} is thrown. This can arise,
|
||||
* for example, when a legal Java identifier name cannot be derived from
|
||||
* the file name of the JAR file or where a package name derived from an
|
||||
* entry ending with {@code .class} is not a legal Java identifier. </p>
|
||||
* the file name of the JAR file or where the JAR file contains a {@code
|
||||
* .class} in the top-level directory of the JAR file. </p>
|
||||
*
|
||||
* <p> In addition to JAR files, an implementation may also support modules
|
||||
* that are packaged in other implementation specific module formats. When
|
||||
@ -283,8 +288,10 @@ public interface ModuleFinder {
|
||||
*
|
||||
* <p> As with automatic modules, the contents of a packaged or exploded
|
||||
* module may need to be <em>scanned</em> in order to determine the packages
|
||||
* in the module. If a {@code .class} file that corresponds to a class in an
|
||||
* unnamed package is encountered then {@code FindException} is thrown. </p>
|
||||
* in the module. If a {@code .class} file (other than {@code
|
||||
* module-info.class}) is found in the top-level directory then it is
|
||||
* assumed to be a class in the unnamed package and so {@code FindException}
|
||||
* is thrown. </p>
|
||||
*
|
||||
* <p> Finders created by this method are lazy and do not eagerly check
|
||||
* that the given file paths are directories or packaged modules.
|
||||
@ -341,7 +348,7 @@ public interface ModuleFinder {
|
||||
* @return A {@code ModuleFinder} that composes a sequence of module finders
|
||||
*/
|
||||
static ModuleFinder compose(ModuleFinder... finders) {
|
||||
// copy the list, also checking for nulls
|
||||
// copy the list and check for nulls
|
||||
final List<ModuleFinder> finderList = List.of(finders);
|
||||
|
||||
return new ModuleFinder() {
|
||||
|
@ -33,10 +33,12 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.module.ModuleDescriptor.Requires;
|
||||
import java.net.URI;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@ -52,49 +54,53 @@ import java.util.jar.Manifest;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import jdk.internal.jmod.JmodFile;
|
||||
import jdk.internal.jmod.JmodFile.Section;
|
||||
import jdk.internal.module.ConfigurableModuleFinder;
|
||||
import jdk.internal.module.Checks;
|
||||
import jdk.internal.perf.PerfCounter;
|
||||
import jdk.internal.util.jar.VersionedStream;
|
||||
|
||||
|
||||
/**
|
||||
* A {@code ModuleFinder} that locates modules on the file system by searching
|
||||
* a sequence of directories or packaged modules.
|
||||
*
|
||||
* The {@code ModuleFinder} can be configured to work in either the run-time
|
||||
* The {@code ModuleFinder} can be created to work in either the run-time
|
||||
* or link-time phases. In both cases it locates modular JAR and exploded
|
||||
* modules. When configured for link-time then it additionally locates
|
||||
* modules. When created for link-time then it additionally locates
|
||||
* modules in JMOD files.
|
||||
*/
|
||||
|
||||
class ModulePath implements ConfigurableModuleFinder {
|
||||
class ModulePath implements ModuleFinder {
|
||||
private static final String MODULE_INFO = "module-info.class";
|
||||
|
||||
// the version to use for multi-release modular JARs
|
||||
private final Runtime.Version releaseVersion;
|
||||
|
||||
// true for the link phase (supports modules packaged in JMOD format)
|
||||
private final boolean isLinkPhase;
|
||||
|
||||
// the entries on this module path
|
||||
private final Path[] entries;
|
||||
private int next;
|
||||
|
||||
// true if in the link phase
|
||||
private boolean isLinkPhase;
|
||||
|
||||
// map of module name to module reference map for modules already located
|
||||
private final Map<String, ModuleReference> cachedModules = new HashMap<>();
|
||||
|
||||
ModulePath(Path... entries) {
|
||||
ModulePath(Runtime.Version version, boolean isLinkPhase, Path... entries) {
|
||||
this.releaseVersion = version;
|
||||
this.isLinkPhase = isLinkPhase;
|
||||
this.entries = entries.clone();
|
||||
for (Path entry : this.entries) {
|
||||
Objects.requireNonNull(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configurePhase(Phase phase) {
|
||||
isLinkPhase = (phase == Phase.LINK_TIME);
|
||||
ModulePath(Path... entries) {
|
||||
this(JarFile.runtimeVersion(), false, entries);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -239,9 +245,13 @@ class ModulePath implements ConfigurableModuleFinder {
|
||||
if (mref != null) {
|
||||
// can have at most one version of a module in the directory
|
||||
String name = mref.descriptor().name();
|
||||
if (nameToReference.put(name, mref) != null) {
|
||||
ModuleReference previous = nameToReference.put(name, mref);
|
||||
if (previous != null) {
|
||||
String fn1 = fileName(mref);
|
||||
String fn2 = fileName(previous);
|
||||
throw new FindException("Two versions of module "
|
||||
+ name + " found in " + dir);
|
||||
+ name + " found in " + dir
|
||||
+ " (" + fn1 + " and " + fn2 + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -294,6 +304,25 @@ class ModulePath implements ConfigurableModuleFinder {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string with the file name of the module if possible.
|
||||
* If the module location is not a file URI then return the URI
|
||||
* as a string.
|
||||
*/
|
||||
private String fileName(ModuleReference mref) {
|
||||
URI uri = mref.location().orElse(null);
|
||||
if (uri != null) {
|
||||
if (uri.getScheme().equalsIgnoreCase("file")) {
|
||||
Path file = Paths.get(uri);
|
||||
return file.getFileName().toString();
|
||||
} else {
|
||||
return uri.toString();
|
||||
}
|
||||
} else {
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
// -- jmod files --
|
||||
|
||||
private Set<String> jmodPackages(JmodFile jf) {
|
||||
@ -301,7 +330,7 @@ class ModulePath implements ConfigurableModuleFinder {
|
||||
.filter(e -> e.section() == Section.CLASSES)
|
||||
.map(JmodFile.Entry::name)
|
||||
.map(this::toPackageName)
|
||||
.filter(pkg -> pkg.length() > 0) // module-info
|
||||
.flatMap(Optional::stream)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@ -328,8 +357,8 @@ class ModulePath implements ConfigurableModuleFinder {
|
||||
private static final String SERVICES_PREFIX = "META-INF/services/";
|
||||
|
||||
/**
|
||||
* Returns a container with the service type corresponding to the name of
|
||||
* a services configuration file.
|
||||
* Returns the service type corresponding to the name of a services
|
||||
* configuration file if it is a valid Java identifier.
|
||||
*
|
||||
* For example, if called with "META-INF/services/p.S" then this method
|
||||
* returns a container with the value "p.S".
|
||||
@ -341,7 +370,8 @@ class ModulePath implements ConfigurableModuleFinder {
|
||||
String prefix = cf.substring(0, index);
|
||||
if (prefix.equals(SERVICES_PREFIX)) {
|
||||
String sn = cf.substring(index);
|
||||
return Optional.of(sn);
|
||||
if (Checks.isJavaIdentifier(sn))
|
||||
return Optional.of(sn);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
@ -416,28 +446,28 @@ class ModulePath implements ConfigurableModuleFinder {
|
||||
if (vs != null)
|
||||
builder.version(vs);
|
||||
|
||||
// scan the entries in the JAR file to locate the .class and service
|
||||
// configuration file
|
||||
Map<Boolean, Set<String>> map =
|
||||
versionedStream(jf)
|
||||
.map(JarEntry::getName)
|
||||
.filter(s -> (s.endsWith(".class") ^ s.startsWith(SERVICES_PREFIX)))
|
||||
.collect(Collectors.partitioningBy(s -> s.endsWith(".class"),
|
||||
Collectors.toSet()));
|
||||
Set<String> classFiles = map.get(Boolean.TRUE);
|
||||
Set<String> configFiles = map.get(Boolean.FALSE);
|
||||
// scan the names of the entries in the JAR file
|
||||
Map<Boolean, Set<String>> map = VersionedStream.stream(jf)
|
||||
.filter(e -> !e.isDirectory())
|
||||
.map(JarEntry::getName)
|
||||
.collect(Collectors.partitioningBy(e -> e.startsWith(SERVICES_PREFIX),
|
||||
Collectors.toSet()));
|
||||
|
||||
Set<String> resources = map.get(Boolean.FALSE);
|
||||
Set<String> configFiles = map.get(Boolean.TRUE);
|
||||
|
||||
// all packages are exported
|
||||
classFiles.stream()
|
||||
.map(c -> toPackageName(c))
|
||||
.distinct()
|
||||
.forEach(builder::exports);
|
||||
resources.stream()
|
||||
.map(this::toPackageName)
|
||||
.flatMap(Optional::stream)
|
||||
.distinct()
|
||||
.forEach(builder::exports);
|
||||
|
||||
// map names of service configuration files to service names
|
||||
Set<String> serviceNames = configFiles.stream()
|
||||
.map(this::toServiceName)
|
||||
.flatMap(Optional::stream)
|
||||
.collect(Collectors.toSet());
|
||||
.map(this::toServiceName)
|
||||
.flatMap(Optional::stream)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// parse each service configuration file
|
||||
for (String sn : serviceNames) {
|
||||
@ -502,25 +532,13 @@ class ModulePath implements ConfigurableModuleFinder {
|
||||
return mn;
|
||||
}
|
||||
|
||||
private Stream<JarEntry> versionedStream(JarFile jf) {
|
||||
if (jf.isMultiRelease()) {
|
||||
// a stream of JarEntries whose names are base names and whose
|
||||
// contents are from the corresponding versioned entries in
|
||||
// a multi-release jar file
|
||||
return jf.stream().map(JarEntry::getName)
|
||||
.filter(name -> !name.startsWith("META-INF/versions/"))
|
||||
.map(jf::getJarEntry);
|
||||
} else {
|
||||
return jf.stream();
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> jarPackages(JarFile jf) {
|
||||
return versionedStream(jf)
|
||||
.filter(e -> e.getName().endsWith(".class"))
|
||||
.map(e -> toPackageName(e.getName()))
|
||||
.filter(pkg -> pkg.length() > 0) // module-info
|
||||
.collect(Collectors.toSet());
|
||||
return VersionedStream.stream(jf)
|
||||
.filter(e -> !e.isDirectory())
|
||||
.map(JarEntry::getName)
|
||||
.map(this::toPackageName)
|
||||
.flatMap(Optional::stream)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -535,7 +553,7 @@ class ModulePath implements ConfigurableModuleFinder {
|
||||
try (JarFile jf = new JarFile(file.toFile(),
|
||||
true, // verify
|
||||
ZipFile.OPEN_READ,
|
||||
JarFile.runtimeVersion()))
|
||||
releaseVersion))
|
||||
{
|
||||
ModuleDescriptor md;
|
||||
JarEntry entry = jf.getJarEntry(MODULE_INFO);
|
||||
@ -565,11 +583,11 @@ class ModulePath implements ConfigurableModuleFinder {
|
||||
private Set<String> explodedPackages(Path dir) {
|
||||
try {
|
||||
return Files.find(dir, Integer.MAX_VALUE,
|
||||
((path, attrs) -> attrs.isRegularFile() &&
|
||||
path.toString().endsWith(".class")))
|
||||
.map(path -> toPackageName(dir.relativize(path)))
|
||||
.filter(pkg -> pkg.length() > 0) // module-info
|
||||
.collect(Collectors.toSet());
|
||||
((path, attrs) -> attrs.isRegularFile()))
|
||||
.map(path -> dir.relativize(path))
|
||||
.map(this::toPackageName)
|
||||
.flatMap(Optional::stream)
|
||||
.collect(Collectors.toSet());
|
||||
} catch (IOException x) {
|
||||
throw new UncheckedIOException(x);
|
||||
}
|
||||
@ -595,29 +613,62 @@ class ModulePath implements ConfigurableModuleFinder {
|
||||
return ModuleReferences.newExplodedModule(md, dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the name of an entry in a JAR or ZIP file to a package name.
|
||||
*
|
||||
* @throws IllegalArgumentException if the name is a class file in
|
||||
* the top-level directory of the JAR/ZIP file (and it's
|
||||
* not module-info.class)
|
||||
*/
|
||||
private Optional<String> toPackageName(String name) {
|
||||
assert !name.endsWith("/");
|
||||
|
||||
//
|
||||
int index = name.lastIndexOf("/");
|
||||
if (index == -1) {
|
||||
if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
|
||||
throw new IllegalArgumentException(name
|
||||
+ " found in top-level directory:"
|
||||
+ " (unnamed package not allowed in module)");
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// p/q/T.class => p.q
|
||||
private String toPackageName(String cn) {
|
||||
assert cn.endsWith(".class");
|
||||
int start = 0;
|
||||
int index = cn.lastIndexOf("/");
|
||||
if (index > start) {
|
||||
return cn.substring(start, index).replace('/', '.');
|
||||
String pn = name.substring(0, index).replace('/', '.');
|
||||
if (Checks.isJavaIdentifier(pn)) {
|
||||
return Optional.of(pn);
|
||||
} else {
|
||||
return "";
|
||||
// not a valid package name
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private String toPackageName(Path path) {
|
||||
String name = path.toString();
|
||||
assert name.endsWith(".class");
|
||||
int index = name.lastIndexOf(File.separatorChar);
|
||||
if (index != -1) {
|
||||
return name.substring(0, index).replace(File.separatorChar, '.');
|
||||
/**
|
||||
* Maps the relative path of an entry in an exploded module to a package
|
||||
* name.
|
||||
*
|
||||
* @throws IllegalArgumentException if the name is a class file in
|
||||
* the top-level directory (and it's not module-info.class)
|
||||
*/
|
||||
private Optional<String> toPackageName(Path file) {
|
||||
assert file.getRoot() == null;
|
||||
|
||||
Path parent = file.getParent();
|
||||
if (parent == null) {
|
||||
String name = file.toString();
|
||||
if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
|
||||
throw new IllegalArgumentException(name
|
||||
+ " found in in top-level directory"
|
||||
+ " (unnamed package not allowed in module)");
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String pn = parent.toString().replace(File.separatorChar, '.');
|
||||
if (Checks.isJavaIdentifier(pn)) {
|
||||
return Optional.of(pn);
|
||||
} else {
|
||||
return "";
|
||||
// not a valid package name
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
/**
|
||||
@ -44,6 +45,11 @@ import java.util.Optional;
|
||||
* module. A module reader is also intended to be used by {@code ClassLoader}
|
||||
* implementations that load classes and resources from modules. </p>
|
||||
*
|
||||
* <p> A resource in a module is identified by a name that is a
|
||||
* '{@code /}'-separated path string. For example, module {@code java.base} may
|
||||
* have a resource "{@code java/lang/Object.class}" that, by convention, is the
|
||||
* class file for {@code java.lang.Object}. </p>
|
||||
*
|
||||
* <p> A {@code ModuleReader} is {@linkplain ModuleReference#open open} upon
|
||||
* creation and is closed by invoking the {@link #close close} method. Failure
|
||||
* to close a module reader may result in a resource leak. The {@code
|
||||
@ -52,8 +58,8 @@ import java.util.Optional;
|
||||
*
|
||||
* <p> A {@code ModuleReader} implementation may require permissions to access
|
||||
* resources in the module. Consequently the {@link #find find}, {@link #open
|
||||
* open} and {@link #read read} methods may throw {@code SecurityException} if
|
||||
* access is denied by the security manager. </p>
|
||||
* open}, {@link #read read}, and {@link #list list} methods may throw {@code
|
||||
* SecurityException} if access is denied by the security manager. </p>
|
||||
*
|
||||
* @see ModuleReference
|
||||
* @since 9
|
||||
@ -84,6 +90,9 @@ public interface ModuleReader extends Closeable {
|
||||
* Opens a resource, returning an input stream to read the resource in
|
||||
* the module.
|
||||
*
|
||||
* <p> The behavior of the input stream when used after the module reader
|
||||
* is closed is implementation specific and therefore not specified. </p>
|
||||
*
|
||||
* @implSpec The default implementation invokes the {@link #find(String)
|
||||
* find} method to get a URI to the resource. If found, then it attempts
|
||||
* to construct a {@link java.net.URL URL} and open a connection to the
|
||||
@ -171,18 +180,38 @@ public interface ModuleReader extends Closeable {
|
||||
Objects.requireNonNull(bb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the contents of the module, returning a stream of elements that
|
||||
* are the names of all resources in the module.
|
||||
*
|
||||
* <p> In lazy implementations then an {@code IOException} may be thrown
|
||||
* when using the stream to list the module contents. If this occurs then
|
||||
* the {@code IOException} will be wrapped in an {@link
|
||||
* java.io.UncheckedIOException} and thrown from the method that caused the
|
||||
* access to be attempted. {@code SecurityException} may also be thrown
|
||||
* when using the stream to list the module contents and access is denied
|
||||
* by the security manager. </p>
|
||||
*
|
||||
* <p> The behavior of the stream when used after the module reader is
|
||||
* closed is implementation specific and therefore not specified. </p>
|
||||
*
|
||||
* @return A stream of elements that are the names of all resources
|
||||
* in the module
|
||||
*
|
||||
* @throws IOException
|
||||
* If an I/O error occurs or the module reader is closed
|
||||
* @throws SecurityException
|
||||
* If denied by the security manager
|
||||
*/
|
||||
Stream<String> list() throws IOException;
|
||||
|
||||
/**
|
||||
* Closes the module reader. Once closed then subsequent calls to locate or
|
||||
* read a resource will fail by returning {@code Optional.empty()} or
|
||||
* throwing {@code IOException}.
|
||||
* read a resource will fail by throwing {@code IOException}.
|
||||
*
|
||||
* <p> A module reader is not required to be asynchronously closeable. If a
|
||||
* thread is reading a resource and another thread invokes the close method,
|
||||
* then the second thread may block until the read operation is complete.
|
||||
*
|
||||
* <p> The behavior of {@code InputStream}s obtained using the {@link
|
||||
* #open(String) open} method and used after the module reader is closed
|
||||
* is implementation specific and therefore not specified.
|
||||
* then the second thread may block until the read operation is complete. </p>
|
||||
*/
|
||||
@Override
|
||||
void close() throws IOException;
|
||||
|
@ -35,6 +35,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
@ -43,14 +44,17 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import jdk.internal.jmod.JmodFile;
|
||||
import jdk.internal.misc.JavaLangAccess;
|
||||
import jdk.internal.misc.SharedSecrets;
|
||||
import jdk.internal.module.ModuleHashes;
|
||||
import jdk.internal.module.ModuleHashes.HashSupplier;
|
||||
import jdk.internal.module.ModulePatcher;
|
||||
import jdk.internal.util.jar.VersionedStream;
|
||||
import sun.net.www.ParseUtil;
|
||||
|
||||
|
||||
@ -139,6 +143,13 @@ class ModuleReferences {
|
||||
*/
|
||||
abstract Optional<InputStream> implOpen(String name) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a stream of the names of resources in the module. This
|
||||
* method is invoked by the list method to do the actual work of
|
||||
* creating the stream.
|
||||
*/
|
||||
abstract Stream<String> implList() throws IOException;
|
||||
|
||||
/**
|
||||
* Closes the module reader. This method is invoked by close to do the
|
||||
* actual work of closing the module reader.
|
||||
@ -175,7 +186,21 @@ class ModuleReferences {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
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) {
|
||||
@ -240,6 +265,16 @@ class ModuleReferences {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Stream<String> implList() throws IOException {
|
||||
// take snapshot to avoid async close
|
||||
List<String> names = VersionedStream.stream(jf)
|
||||
.filter(e -> !e.isDirectory())
|
||||
.map(JarEntry::getName)
|
||||
.collect(Collectors.toList());
|
||||
return names.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
void implClose() throws IOException {
|
||||
jf.close();
|
||||
@ -251,30 +286,31 @@ class ModuleReferences {
|
||||
* A ModuleReader for a JMOD file.
|
||||
*/
|
||||
static class JModModuleReader extends SafeCloseModuleReader {
|
||||
private final ZipFile zf;
|
||||
private final JmodFile jf;
|
||||
private final URI uri;
|
||||
|
||||
static ZipFile newZipFile(Path path) {
|
||||
static JmodFile newJmodFile(Path path) {
|
||||
try {
|
||||
return new ZipFile(path.toFile());
|
||||
return new JmodFile(path);
|
||||
} catch (IOException ioe) {
|
||||
throw new UncheckedIOException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
JModModuleReader(Path path, URI uri) {
|
||||
this.zf = newZipFile(path);
|
||||
this.jf = newJmodFile(path);
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
private ZipEntry getEntry(String name) {
|
||||
return zf.getEntry("classes/" + Objects.requireNonNull(name));
|
||||
private JmodFile.Entry getEntry(String name) {
|
||||
Objects.requireNonNull(name);
|
||||
return jf.getEntry(JmodFile.Section.CLASSES, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<URI> implFind(String name) {
|
||||
ZipEntry ze = getEntry(name);
|
||||
if (ze != null) {
|
||||
JmodFile.Entry je = getEntry(name);
|
||||
if (je != null) {
|
||||
String encodedPath = ParseUtil.encodePath(name, false);
|
||||
String uris = "jmod:" + uri + "!/" + encodedPath;
|
||||
return Optional.of(URI.create(uris));
|
||||
@ -285,17 +321,27 @@ class ModuleReferences {
|
||||
|
||||
@Override
|
||||
Optional<InputStream> implOpen(String name) throws IOException {
|
||||
ZipEntry ze = getEntry(name);
|
||||
if (ze != null) {
|
||||
return Optional.of(zf.getInputStream(ze));
|
||||
JmodFile.Entry je = getEntry(name);
|
||||
if (je != null) {
|
||||
return Optional.of(jf.getInputStream(je));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Stream<String> implList() throws IOException {
|
||||
// take snapshot to avoid async close
|
||||
List<String> names = jf.stream()
|
||||
.filter(e -> e.section() == JmodFile.Section.CLASSES)
|
||||
.map(JmodFile.Entry::name)
|
||||
.collect(Collectors.toList());
|
||||
return names.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
void implClose() throws IOException {
|
||||
zf.close();
|
||||
jf.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -377,6 +423,17 @@ class ModuleReferences {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> list() throws IOException {
|
||||
ensureOpen();
|
||||
// sym links not followed
|
||||
return Files.find(dir, Integer.MAX_VALUE,
|
||||
(path, attrs) -> attrs.isRegularFile())
|
||||
.map(f -> dir.relativize(f)
|
||||
.toString()
|
||||
.replace(File.separatorChar, '/'));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
closed = true;
|
||||
|
@ -32,14 +32,21 @@ import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterator;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import jdk.internal.jimage.ImageLocation;
|
||||
import jdk.internal.jimage.ImageReader;
|
||||
@ -62,6 +69,8 @@ import jdk.internal.perf.PerfCounter;
|
||||
|
||||
class SystemModuleFinder implements ModuleFinder {
|
||||
|
||||
private static final JavaNetUriAccess JNUA = SharedSecrets.getJavaNetUriAccess();
|
||||
|
||||
private static final PerfCounter initTime
|
||||
= PerfCounter.newPerfCounter("jdk.module.finder.jimage.initTime");
|
||||
private static final PerfCounter moduleCount
|
||||
@ -73,8 +82,6 @@ class SystemModuleFinder implements ModuleFinder {
|
||||
// ImageReader used to access all modules in the image
|
||||
private static final ImageReader imageReader;
|
||||
|
||||
private static final JavaNetUriAccess jnua = SharedSecrets.getJavaNetUriAccess();
|
||||
|
||||
// the set of modules in the run-time image
|
||||
private static final Set<ModuleReference> modules;
|
||||
|
||||
@ -170,8 +177,7 @@ class SystemModuleFinder implements ModuleFinder {
|
||||
HashSupplier hash)
|
||||
{
|
||||
String mn = md.name();
|
||||
|
||||
URI uri = jnua.create("jrt", "/".concat(mn));
|
||||
URI uri = JNUA.create("jrt", "/".concat(mn));
|
||||
|
||||
Supplier<ModuleReader> readerSupplier = new Supplier<>() {
|
||||
@Override
|
||||
@ -331,6 +337,15 @@ class SystemModuleFinder implements ModuleFinder {
|
||||
ImageReader.releaseByteBuffer(bb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> list() throws IOException {
|
||||
if (closed)
|
||||
throw new IOException("ModuleReader is closed");
|
||||
|
||||
Spliterator<String> s = new ModuleContentSpliterator(module);
|
||||
return StreamSupport.stream(s, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// nothing else to do
|
||||
@ -338,4 +353,86 @@ class SystemModuleFinder implements ModuleFinder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Spliterator for traversing the resources of a module linked into the
|
||||
* run-time image.
|
||||
*/
|
||||
static class ModuleContentSpliterator implements Spliterator<String> {
|
||||
final String moduleRoot;
|
||||
final Deque<ImageReader.Node> stack;
|
||||
Iterator<ImageReader.Node> iterator;
|
||||
|
||||
ModuleContentSpliterator(String module) throws IOException {
|
||||
moduleRoot = "/modules/" + module;
|
||||
stack = new ArrayDeque<>();
|
||||
|
||||
// push the root node to the stack to get started
|
||||
ImageReader.Node dir = imageReader.findNode(moduleRoot);
|
||||
if (dir == null || !dir.isDirectory())
|
||||
throw new IOException(moduleRoot + " not a directory");
|
||||
stack.push(dir);
|
||||
iterator = Collections.emptyIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the next non-directory node or {@code null} if
|
||||
* there are no remaining nodes to visit.
|
||||
*/
|
||||
private String next() throws IOException {
|
||||
for (;;) {
|
||||
while (iterator.hasNext()) {
|
||||
ImageReader.Node node = iterator.next();
|
||||
String name = node.getName();
|
||||
if (node.isDirectory()) {
|
||||
// build node
|
||||
ImageReader.Node dir = imageReader.findNode(name);
|
||||
assert dir.isDirectory();
|
||||
stack.push(dir);
|
||||
} else {
|
||||
// strip /modules/$MODULE/ prefix
|
||||
return name.substring(moduleRoot.length() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (stack.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
ImageReader.Node dir = stack.poll();
|
||||
assert dir.isDirectory();
|
||||
iterator = dir.getChildren().iterator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryAdvance(Consumer<? super String> action) {
|
||||
String next;
|
||||
try {
|
||||
next = next();
|
||||
} catch (IOException ioe) {
|
||||
throw new UncheckedIOException(ioe);
|
||||
}
|
||||
if (next != null) {
|
||||
action.accept(next);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<String> trySplit() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int characteristics() {
|
||||
return Spliterator.DISTINCT + Spliterator.NONNULL + Spliterator.IMMUTABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long estimateSize() {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,6 +175,16 @@ public class JmodFile implements AutoCloseable {
|
||||
this.zipfile = new ZipFile(file.toFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Entry} for a resource in a JMOD file section
|
||||
* or {@code null} if not found.
|
||||
*/
|
||||
public Entry getEntry(Section section, String name) {
|
||||
String entry = section.jmodDir() + "/" + name;
|
||||
ZipEntry ze = zipfile.getEntry(entry);
|
||||
return (ze != null) ? new Entry(ze) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an {@code InputStream} for reading the named entry of the given
|
||||
* section in this jmod file.
|
||||
@ -185,7 +195,6 @@ public class JmodFile implements AutoCloseable {
|
||||
public InputStream getInputStream(Section section, String name)
|
||||
throws IOException
|
||||
{
|
||||
|
||||
String entry = section.jmodDir() + "/" + name;
|
||||
ZipEntry e = zipfile.getEntry(entry);
|
||||
if (e == null) {
|
||||
@ -194,6 +203,15 @@ public class JmodFile implements AutoCloseable {
|
||||
return zipfile.getInputStream(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an {@code InputStream} for reading an entry in the JMOD file.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public InputStream getInputStream(Entry entry) throws IOException {
|
||||
return zipfile.getInputStream(entry.zipEntry());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of non-directory entries in this jmod file.
|
||||
*/
|
||||
|
@ -53,6 +53,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.module.ModulePatcher.PatchedModuleReader;
|
||||
import jdk.internal.misc.VM;
|
||||
@ -749,6 +750,10 @@ public class BuiltinClassLoader
|
||||
return Optional.empty();
|
||||
}
|
||||
@Override
|
||||
public Stream<String> list() {
|
||||
return Stream.empty();
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
throw new InternalError("Should not get here");
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
/**
|
||||
@ -534,6 +535,10 @@ public final class Loader extends SecureClassLoader {
|
||||
return Optional.empty();
|
||||
}
|
||||
@Override
|
||||
public Stream<String> list() {
|
||||
return Stream.empty();
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
throw new InternalError("Should not get here");
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import java.util.Collection;
|
||||
import java.lang.module.ModuleReader;
|
||||
import java.lang.module.ModuleReference;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@ -102,6 +103,11 @@ public interface JavaLangModuleAccess {
|
||||
Set<String> packages,
|
||||
ModuleHashes hashes);
|
||||
|
||||
/**
|
||||
* Returns the object with the hashes of other modules
|
||||
*/
|
||||
Optional<ModuleHashes> hashes(ModuleDescriptor descriptor);
|
||||
|
||||
/**
|
||||
* Resolves a collection of root modules, with service binding
|
||||
* and the empty configuration as the parent. The post resolution
|
||||
@ -120,8 +126,10 @@ public interface JavaLangModuleAccess {
|
||||
Supplier<ModuleReader> readerSupplier);
|
||||
|
||||
/**
|
||||
* Returns the object with the hashes of other modules
|
||||
* Creates a ModuleFinder for a module path.
|
||||
*/
|
||||
Optional<ModuleHashes> hashes(ModuleDescriptor descriptor);
|
||||
ModuleFinder newModulePath(Runtime.Version version,
|
||||
boolean isLinkPhase,
|
||||
Path... entries);
|
||||
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.internal.module;
|
||||
|
||||
import java.lang.module.ModuleFinder;
|
||||
|
||||
/**
|
||||
* A ModuleFinder that may be configured to work at either run-time
|
||||
* or link-time.
|
||||
*/
|
||||
|
||||
public interface ConfigurableModuleFinder extends ModuleFinder {
|
||||
|
||||
public static enum Phase {
|
||||
RUN_TIME,
|
||||
LINK_TIME
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures this finder to work in the given phase.
|
||||
*/
|
||||
void configurePhase(Phase phase);
|
||||
|
||||
}
|
@ -57,7 +57,15 @@ public final class ModuleInfoWriter {
|
||||
cw.visit(Opcodes.V1_9, ACC_MODULE, name, null, null, null);
|
||||
|
||||
cw.visitAttribute(new ModuleAttribute(md));
|
||||
cw.visitAttribute(new ConcealedPackagesAttribute(md.conceals()));
|
||||
|
||||
// for tests: write the ConcealedPackages attribute when there are non-exported packages
|
||||
long nExportedPackages = md.exports().stream()
|
||||
.map(ModuleDescriptor.Exports::source)
|
||||
.distinct()
|
||||
.count();
|
||||
if (md.packages().size() > nExportedPackages)
|
||||
cw.visitAttribute(new ConcealedPackagesAttribute(md.packages()));
|
||||
|
||||
md.version().ifPresent(v -> cw.visitAttribute(new VersionAttribute(v)));
|
||||
md.mainClass().ifPresent(mc -> cw.visitAttribute(new MainClassAttribute(mc)));
|
||||
|
||||
|
@ -50,6 +50,7 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.loader.Resource;
|
||||
import jdk.internal.misc.JavaLangModuleAccess;
|
||||
@ -159,21 +160,19 @@ public final class ModulePatcher {
|
||||
// is not supported by the boot class loader
|
||||
try (JarFile jf = new JarFile(file.toFile())) {
|
||||
jf.stream()
|
||||
.filter(e -> e.getName().endsWith(".class"))
|
||||
.map(e -> toPackageName(file, e))
|
||||
.filter(pn -> pn.length() > 0)
|
||||
.filter(Checks::isJavaIdentifier)
|
||||
.forEach(packages::add);
|
||||
}
|
||||
|
||||
} else if (Files.isDirectory(file)) {
|
||||
|
||||
// exploded directory
|
||||
// exploded directory without following sym links
|
||||
Path top = file;
|
||||
Files.find(top, Integer.MAX_VALUE,
|
||||
((path, attrs) -> attrs.isRegularFile() &&
|
||||
path.toString().endsWith(".class")))
|
||||
((path, attrs) -> attrs.isRegularFile()))
|
||||
.map(path -> toPackageName(top, path))
|
||||
.filter(pn -> pn.length() > 0)
|
||||
.filter(Checks::isJavaIdentifier)
|
||||
.forEach(packages::add);
|
||||
|
||||
}
|
||||
@ -380,6 +379,15 @@ public final class ModulePatcher {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> list() throws IOException {
|
||||
Stream<String> s = delegate().list();
|
||||
for (ResourceFinder finder : finders) {
|
||||
s = Stream.concat(s, finder.list());
|
||||
}
|
||||
return s.distinct();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
closeAll(finders);
|
||||
@ -393,6 +401,7 @@ public final class ModulePatcher {
|
||||
*/
|
||||
private static interface ResourceFinder extends Closeable {
|
||||
Resource find(String name) throws IOException;
|
||||
Stream<String> list() throws IOException;
|
||||
}
|
||||
|
||||
|
||||
@ -453,6 +462,13 @@ public final class ModulePatcher {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> list() throws IOException {
|
||||
return jf.stream()
|
||||
.filter(e -> !e.isDirectory())
|
||||
.map(JarEntry::getName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -527,6 +543,15 @@ public final class ModulePatcher {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> list() throws IOException {
|
||||
return Files.find(dir, Integer.MAX_VALUE,
|
||||
(path, attrs) -> attrs.isRegularFile())
|
||||
.map(f -> dir.relativize(f)
|
||||
.toString()
|
||||
.replace(File.separatorChar, '/'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -537,7 +562,7 @@ public final class ModulePatcher {
|
||||
Path entry = top.relativize(file);
|
||||
Path parent = entry.getParent();
|
||||
if (parent == null) {
|
||||
return warnUnnamedPackage(top, entry.toString());
|
||||
return warnIfModuleInfo(top, entry.toString());
|
||||
} else {
|
||||
return parent.toString().replace(File.separatorChar, '.');
|
||||
}
|
||||
@ -557,14 +582,15 @@ public final class ModulePatcher {
|
||||
String name = entry.getName();
|
||||
int index = name.lastIndexOf("/");
|
||||
if (index == -1) {
|
||||
return warnUnnamedPackage(file, name);
|
||||
return warnIfModuleInfo(file, name);
|
||||
} else {
|
||||
return name.substring(0, index).replace('/', '.');
|
||||
}
|
||||
}
|
||||
|
||||
private static String warnUnnamedPackage(Path file, String e) {
|
||||
System.err.println("WARNING: " + e + " not allowed in patch: " + file);
|
||||
private static String warnIfModuleInfo(Path file, String e) {
|
||||
if (e.equals("module-info.class"))
|
||||
System.err.println("WARNING: " + e + " ignored in patch: " + file);
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -42,8 +42,6 @@ import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jdk.internal.module.ConfigurableModuleFinder;
|
||||
import jdk.internal.module.ConfigurableModuleFinder.Phase;
|
||||
import jdk.tools.jlink.internal.TaskHelper.BadArgs;
|
||||
import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE;
|
||||
import jdk.tools.jlink.internal.Jlink.JlinkConfiguration;
|
||||
@ -54,6 +52,7 @@ import jdk.tools.jlink.internal.ImagePluginStack.ImageProvider;
|
||||
import jdk.tools.jlink.plugin.PluginException;
|
||||
import jdk.tools.jlink.builder.DefaultImageBuilder;
|
||||
import jdk.tools.jlink.plugin.Plugin;
|
||||
import jdk.internal.misc.SharedSecrets;
|
||||
|
||||
/**
|
||||
* Implementation for the jlink tool.
|
||||
@ -252,8 +251,9 @@ public class JlinkTask {
|
||||
throw new Exception("Empty module paths");
|
||||
}
|
||||
|
||||
ModuleFinder finder
|
||||
= newModuleFinder(config.getModulepaths(), config.getLimitmods(), config.getModules());
|
||||
ModuleFinder finder = newModuleFinder(config.getModulepaths(),
|
||||
config.getLimitmods(),
|
||||
config.getModules());
|
||||
|
||||
// First create the image provider
|
||||
ImageProvider imageProvider
|
||||
@ -328,24 +328,41 @@ public class JlinkTask {
|
||||
return addMods;
|
||||
}
|
||||
|
||||
public static ModuleFinder newModuleFinder(List<Path> paths,
|
||||
Set<String> limitMods,
|
||||
Set<String> addMods)
|
||||
{
|
||||
ModuleFinder finder = ModuleFinder.of(paths.toArray(new Path[0]));
|
||||
/**
|
||||
* Returns a module finder to find the observable modules specified in
|
||||
* the --module-path and --limit-modules options
|
||||
*/
|
||||
private ModuleFinder modulePathFinder() {
|
||||
Path[] entries = options.modulePath.toArray(new Path[0]);
|
||||
ModuleFinder finder = SharedSecrets.getJavaLangModuleAccess()
|
||||
.newModulePath(Runtime.version(), true, entries);
|
||||
|
||||
// jmods are located at link-time
|
||||
if (finder instanceof ConfigurableModuleFinder) {
|
||||
((ConfigurableModuleFinder) finder).configurePhase(Phase.LINK_TIME);
|
||||
}
|
||||
|
||||
// if limitmods is specified then limit the universe
|
||||
if (!limitMods.isEmpty()) {
|
||||
finder = limitFinder(finder, limitMods, addMods);
|
||||
if (!options.limitMods.isEmpty()) {
|
||||
finder = limitFinder(finder, options.limitMods, Collections.emptySet());
|
||||
}
|
||||
return finder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a module finder of the given module path that limits
|
||||
* the observable modules to those in the transitive closure of
|
||||
* the modules specified in {@code limitMods} plus other modules
|
||||
* specified in the {@code roots} set.
|
||||
*/
|
||||
public static ModuleFinder newModuleFinder(List<Path> paths,
|
||||
Set<String> limitMods,
|
||||
Set<String> roots)
|
||||
{
|
||||
Path[] entries = paths.toArray(new Path[0]);
|
||||
ModuleFinder finder = SharedSecrets.getJavaLangModuleAccess()
|
||||
.newModulePath(Runtime.version(), true, entries);
|
||||
|
||||
// if limitmods is specified then limit the universe
|
||||
if (!limitMods.isEmpty()) {
|
||||
finder = limitFinder(finder, limitMods, roots);
|
||||
}
|
||||
return finder;
|
||||
}
|
||||
|
||||
private static Path toPathLocation(ResolvedModule m) {
|
||||
Optional<URI> ouri = m.reference().location();
|
||||
|
@ -47,8 +47,6 @@ import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.internal.module.ConfigurableModuleFinder;
|
||||
import jdk.internal.module.ConfigurableModuleFinder.Phase;
|
||||
import jdk.tools.jlink.internal.plugins.ExcludeFilesPlugin;
|
||||
import jdk.tools.jlink.internal.plugins.ExcludeJmodSectionPlugin;
|
||||
import jdk.tools.jlink.plugin.Plugin;
|
||||
@ -60,6 +58,7 @@ import jdk.tools.jlink.internal.Jlink.PluginsConfiguration;
|
||||
import jdk.tools.jlink.internal.plugins.PluginsResourceBundle;
|
||||
import jdk.tools.jlink.internal.plugins.DefaultCompressPlugin;
|
||||
import jdk.tools.jlink.internal.plugins.StripDebugPlugin;
|
||||
import jdk.internal.misc.SharedSecrets;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -726,14 +725,10 @@ public final class TaskHelper {
|
||||
}
|
||||
|
||||
static Layer createPluginsLayer(List<Path> paths) {
|
||||
Path[] arr = new Path[paths.size()];
|
||||
paths.toArray(arr);
|
||||
ModuleFinder finder = ModuleFinder.of(arr);
|
||||
|
||||
// jmods are located at link-time
|
||||
if (finder instanceof ConfigurableModuleFinder) {
|
||||
((ConfigurableModuleFinder) finder).configurePhase(Phase.LINK_TIME);
|
||||
}
|
||||
Path[] dirs = paths.toArray(new Path[0]);
|
||||
ModuleFinder finder = SharedSecrets.getJavaLangModuleAccess()
|
||||
.newModulePath(Runtime.version(), true, dirs);
|
||||
|
||||
Configuration bootConfiguration = Layer.boot().configuration();
|
||||
try {
|
||||
|
@ -98,8 +98,6 @@ import jdk.internal.joptsimple.OptionSpec;
|
||||
import jdk.internal.joptsimple.ValueConverter;
|
||||
import jdk.internal.misc.JavaLangModuleAccess;
|
||||
import jdk.internal.misc.SharedSecrets;
|
||||
import jdk.internal.module.ConfigurableModuleFinder;
|
||||
import jdk.internal.module.ConfigurableModuleFinder.Phase;
|
||||
import jdk.internal.module.ModuleHashes;
|
||||
import jdk.internal.module.ModuleInfoExtender;
|
||||
import jdk.tools.jlink.internal.Utils;
|
||||
@ -1298,9 +1296,7 @@ public class JmodTask {
|
||||
options.manPages = opts.valuesOf(manPages);
|
||||
if (opts.has(modulePath)) {
|
||||
Path[] dirs = opts.valuesOf(modulePath).toArray(new Path[0]);
|
||||
options.moduleFinder = ModuleFinder.of(dirs);
|
||||
if (options.moduleFinder instanceof ConfigurableModuleFinder)
|
||||
((ConfigurableModuleFinder)options.moduleFinder).configurePhase(Phase.LINK_TIME);
|
||||
options.moduleFinder = JLMA.newModulePath(Runtime.version(), true, dirs);
|
||||
}
|
||||
if (opts.has(moduleVersion))
|
||||
options.moduleVersion = opts.valueOf(moduleVersion);
|
||||
|
@ -158,39 +158,84 @@ public class AutomaticModulesTest {
|
||||
*/
|
||||
public void testPackages() throws IOException {
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mods");
|
||||
createDummyJarFile(dir.resolve("m1.jar"),
|
||||
createDummyJarFile(dir.resolve("m.jar"),
|
||||
"p/C1.class", "p/C2.class", "q/C1.class");
|
||||
|
||||
ModuleFinder finder = ModuleFinder.of(dir);
|
||||
Optional<ModuleReference> mref = finder.find("m");
|
||||
assertTrue(mref.isPresent(), "m not found");
|
||||
|
||||
Configuration parent = Layer.boot().configuration();
|
||||
Configuration cf = resolve(parent, finder, "m1");
|
||||
ModuleDescriptor descriptor = mref.get().descriptor();
|
||||
|
||||
ModuleDescriptor m1 = findDescriptor(cf, "m1");
|
||||
|
||||
Set<String> exports
|
||||
= m1.exports().stream().map(Exports::source).collect(Collectors.toSet());
|
||||
assertTrue(descriptor.packages().size() == 2);
|
||||
assertTrue(descriptor.packages().contains("p"));
|
||||
assertTrue(descriptor.packages().contains("q"));
|
||||
|
||||
Set<String> exports = descriptor.exports().stream()
|
||||
.map(Exports::source)
|
||||
.collect(Collectors.toSet());
|
||||
assertTrue(exports.size() == 2);
|
||||
assertTrue(exports.contains("p"));
|
||||
assertTrue(exports.contains("q"));
|
||||
assertTrue(m1.conceals().isEmpty());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test class file in JAR file where the entry does not correspond to a
|
||||
* Test class files in JAR file where the entry does not correspond to a
|
||||
* legal package name.
|
||||
*/
|
||||
@Test(expectedExceptions = FindException.class)
|
||||
public void testBadPackage() throws IOException {
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mods");
|
||||
createDummyJarFile(dir.resolve("m1.jar"), "p-/T.class");
|
||||
createDummyJarFile(dir.resolve("m.jar"), "p/C1.class", "p-/C2.class");
|
||||
|
||||
// should throw FindException
|
||||
ModuleFinder.of(dir).findAll();
|
||||
ModuleFinder finder = ModuleFinder.of(dir);
|
||||
Optional<ModuleReference> mref = finder.find("m");
|
||||
assertTrue(mref.isPresent(), "m not found");
|
||||
|
||||
ModuleDescriptor descriptor = mref.get().descriptor();
|
||||
|
||||
assertTrue(descriptor.packages().size() == 1);
|
||||
assertTrue(descriptor.packages().contains("p"));
|
||||
|
||||
Set<String> exports = descriptor.exports().stream()
|
||||
.map(Exports::source)
|
||||
.collect(Collectors.toSet());
|
||||
assertTrue(exports.size() == 1);
|
||||
assertTrue(exports.contains("p"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test non-class resources in a JAR file.
|
||||
*/
|
||||
public void testNonClassResources() throws IOException {
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mods");
|
||||
createDummyJarFile(dir.resolve("m.jar"),
|
||||
"LICENSE",
|
||||
"README",
|
||||
"WEB-INF/tags",
|
||||
"p/Type.class",
|
||||
"p/resources/m.properties");
|
||||
|
||||
ModuleFinder finder = ModuleFinder.of(dir);
|
||||
Optional<ModuleReference> mref = finder.find("m");
|
||||
assertTrue(mref.isPresent(), "m not found");
|
||||
|
||||
ModuleDescriptor descriptor = mref.get().descriptor();
|
||||
|
||||
assertTrue(descriptor.packages().size() == 2);
|
||||
assertTrue(descriptor.packages().contains("p"));
|
||||
assertTrue(descriptor.packages().contains("p.resources"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test .class file in unnamed package (top-level directory)
|
||||
*/
|
||||
@Test(expectedExceptions = FindException.class)
|
||||
public void testClassInUnnamedPackage() throws IOException {
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mods");
|
||||
createDummyJarFile(dir.resolve("m.jar"), "Mojo.class");
|
||||
ModuleFinder finder = ModuleFinder.of(dir);
|
||||
finder.findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test JAR file with META-INF/services configuration file
|
||||
@ -204,12 +249,12 @@ public class AutomaticModulesTest {
|
||||
Files.createDirectories(services);
|
||||
Files.write(services.resolve(service), Set.of(provider));
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mods");
|
||||
JarUtils.createJarFile(dir.resolve("m1.jar"), tmpdir);
|
||||
JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
|
||||
|
||||
ModuleFinder finder = ModuleFinder.of(dir);
|
||||
|
||||
Optional<ModuleReference> mref = finder.find("m1");
|
||||
assertTrue(mref.isPresent(), "m1 not found");
|
||||
Optional<ModuleReference> mref = finder.find("m");
|
||||
assertTrue(mref.isPresent(), "m not found");
|
||||
|
||||
ModuleDescriptor descriptor = mref.get().descriptor();
|
||||
assertTrue(descriptor.provides().size() == 1);
|
||||
@ -220,17 +265,12 @@ public class AutomaticModulesTest {
|
||||
}
|
||||
|
||||
|
||||
// META-INF/services configuration file/entries that are not legal
|
||||
@DataProvider(name = "badproviders")
|
||||
public Object[][] createProviders() {
|
||||
// META-INF/services files that don't map to legal service names
|
||||
@DataProvider(name = "badservices")
|
||||
public Object[][] createBadServices() {
|
||||
return new Object[][] {
|
||||
|
||||
// service type provider type
|
||||
|
||||
{ "p.S", "-" },
|
||||
{ "p.S", ".S1" },
|
||||
{ "p.S", "S1." },
|
||||
|
||||
{ "-", "p.S1" },
|
||||
{ ".S", "p.S1" },
|
||||
};
|
||||
@ -240,8 +280,8 @@ public class AutomaticModulesTest {
|
||||
* Test JAR file with META-INF/services configuration file with bad
|
||||
* values or names.
|
||||
*/
|
||||
@Test(dataProvider = "badproviders", expectedExceptions = FindException.class)
|
||||
public void testBadServicesConfiguration(String service, String provider)
|
||||
@Test(dataProvider = "badservices")
|
||||
public void testBadServicesNames(String service, String provider)
|
||||
throws IOException
|
||||
{
|
||||
Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
|
||||
@ -249,7 +289,41 @@ public class AutomaticModulesTest {
|
||||
Files.createDirectories(services);
|
||||
Files.write(services.resolve(service), Set.of(provider));
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mods");
|
||||
JarUtils.createJarFile(dir.resolve("m1.jar"), tmpdir);
|
||||
JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
|
||||
|
||||
Optional<ModuleReference> omref = ModuleFinder.of(dir).find("m");
|
||||
assertTrue(omref.isPresent());
|
||||
ModuleDescriptor descriptor = omref.get().descriptor();
|
||||
assertTrue(descriptor.provides().isEmpty());
|
||||
}
|
||||
|
||||
|
||||
// META-INF/services configuration file entries that are not legal
|
||||
@DataProvider(name = "badproviders")
|
||||
public Object[][] createBadProviders() {
|
||||
return new Object[][] {
|
||||
|
||||
// service type provider type
|
||||
{ "p.S", "-" },
|
||||
{ "p.S", ".S1" },
|
||||
{ "p.S", "S1." },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Test JAR file with META-INF/services configuration file with bad
|
||||
* values or names.
|
||||
*/
|
||||
@Test(dataProvider = "badproviders", expectedExceptions = FindException.class)
|
||||
public void testBadProvideNames(String service, String provider)
|
||||
throws IOException
|
||||
{
|
||||
Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
|
||||
Path services = tmpdir.resolve("META-INF").resolve("services");
|
||||
Files.createDirectories(services);
|
||||
Files.write(services.resolve(service), Set.of(provider));
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mods");
|
||||
JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
|
||||
|
||||
// should throw FindException
|
||||
ModuleFinder.of(dir).findAll();
|
||||
|
@ -29,6 +29,7 @@
|
||||
* @summary Basic tests for java.lang.module.ModuleFinder
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.module.FindException;
|
||||
import java.lang.module.InvalidModuleDescriptorException;
|
||||
@ -38,6 +39,7 @@ import java.lang.module.ModuleReference;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
@ -316,6 +318,111 @@ public class ModuleFinderTest {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test ModuleFinder with a JAR file containing a mix of class and
|
||||
* non-class resources.
|
||||
*/
|
||||
public void testOfOneJarFileWithResources() throws Exception {
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mods");
|
||||
Path jar = createModularJar(dir.resolve("m.jar"), "m",
|
||||
"LICENSE",
|
||||
"README",
|
||||
"WEB-INF/tags",
|
||||
"p/Type.class",
|
||||
"p/resources/m.properties",
|
||||
"q-/Type.class", // not a legal package name
|
||||
"q-/resources/m/properties");
|
||||
|
||||
ModuleFinder finder = ModuleFinder.of(jar);
|
||||
Optional<ModuleReference> mref = finder.find("m");
|
||||
assertTrue(mref.isPresent(), "m not found");
|
||||
|
||||
ModuleDescriptor descriptor = mref.get().descriptor();
|
||||
|
||||
assertTrue(descriptor.packages().size() == 2);
|
||||
assertTrue(descriptor.packages().contains("p"));
|
||||
assertTrue(descriptor.packages().contains("p.resources"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test ModuleFinder with an exploded module containing a mix of class
|
||||
* and non-class resources
|
||||
*/
|
||||
public void testOfOneExplodedModuleWithResources() throws Exception {
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mods");
|
||||
Path m_dir = createExplodedModule(dir.resolve("m"), "m",
|
||||
"LICENSE",
|
||||
"README",
|
||||
"WEB-INF/tags",
|
||||
"p/Type.class",
|
||||
"p/resources/m.properties",
|
||||
"q-/Type.class", // not a legal package name
|
||||
"q-/resources/m/properties");
|
||||
|
||||
ModuleFinder finder = ModuleFinder.of(m_dir);
|
||||
Optional<ModuleReference> mref = finder.find("m");
|
||||
assertTrue(mref.isPresent(), "m not found");
|
||||
|
||||
ModuleDescriptor descriptor = mref.get().descriptor();
|
||||
|
||||
assertTrue(descriptor.packages().size() == 2);
|
||||
assertTrue(descriptor.packages().contains("p"));
|
||||
assertTrue(descriptor.packages().contains("p.resources"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test ModuleModule with a JAR file containing a .class file in the top
|
||||
* level directory.
|
||||
*/
|
||||
public void testOfOneJarFileWithTopLevelClass() throws Exception {
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mods");
|
||||
Path jar = createModularJar(dir.resolve("m.jar"), "m", "Mojo.class");
|
||||
|
||||
ModuleFinder finder = ModuleFinder.of(jar);
|
||||
try {
|
||||
finder.find("m");
|
||||
assertTrue(false);
|
||||
} catch (FindException e) {
|
||||
assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
|
||||
}
|
||||
|
||||
finder = ModuleFinder.of(jar);
|
||||
try {
|
||||
finder.findAll();
|
||||
assertTrue(false);
|
||||
} catch (FindException e) {
|
||||
assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ModuleModule with a JAR file containing a .class file in the top
|
||||
* level directory.
|
||||
*/
|
||||
public void testOfOneExplodedModuleWithTopLevelClass() throws Exception {
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mods");
|
||||
Path m_dir = createExplodedModule(dir.resolve("m"), "m", "Mojo.class");
|
||||
|
||||
ModuleFinder finder = ModuleFinder.of(m_dir);
|
||||
try {
|
||||
finder.find("m");
|
||||
assertTrue(false);
|
||||
} catch (FindException e) {
|
||||
assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
|
||||
}
|
||||
|
||||
finder = ModuleFinder.of(m_dir);
|
||||
try {
|
||||
finder.findAll();
|
||||
assertTrue(false);
|
||||
} catch (FindException e) {
|
||||
assertTrue(e.getCause() instanceof InvalidModuleDescriptorException);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test ModuleFinder.of with a path to a file that does not exist.
|
||||
*/
|
||||
@ -641,7 +748,7 @@ public class ModuleFinderTest {
|
||||
vs = mid.substring(i+1);
|
||||
}
|
||||
ModuleDescriptor.Builder builder
|
||||
= new ModuleDescriptor.Builder(mn).requires("java.base");
|
||||
= new ModuleDescriptor.Builder(mn).requires("java.base");
|
||||
if (vs != null)
|
||||
builder.version(vs);
|
||||
return builder.build();
|
||||
@ -651,13 +758,22 @@ public class ModuleFinderTest {
|
||||
* Creates an exploded module in the given directory and containing a
|
||||
* module descriptor with the given module name/version.
|
||||
*/
|
||||
static Path createExplodedModule(Path dir, String mid) throws Exception {
|
||||
static Path createExplodedModule(Path dir, String mid, String... entries)
|
||||
throws Exception
|
||||
{
|
||||
ModuleDescriptor descriptor = newModuleDescriptor(mid);
|
||||
Files.createDirectories(dir);
|
||||
Path mi = dir.resolve("module-info.class");
|
||||
try (OutputStream out = Files.newOutputStream(mi)) {
|
||||
ModuleInfoWriter.write(descriptor, out);
|
||||
}
|
||||
|
||||
for (String entry : entries) {
|
||||
Path file = dir.resolve(entry.replace('/', File.separatorChar));
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.createFile(file);
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
|
@ -24,9 +24,8 @@
|
||||
/**
|
||||
* @test
|
||||
* @library /lib/testlibrary
|
||||
* @modules java.base/jdk.internal.module
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* jdk.compiler
|
||||
* jdk.jlink
|
||||
* @build ModuleReaderTest CompilerUtils JarUtils
|
||||
* @run testng ModuleReaderTest
|
||||
* @summary Basic tests for java.lang.module.ModuleReader
|
||||
@ -47,11 +46,14 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.spi.ToolProvider;
|
||||
|
||||
import jdk.internal.module.ConfigurableModuleFinder;
|
||||
import jdk.internal.module.ConfigurableModuleFinder.Phase;
|
||||
import jdk.internal.misc.SharedSecrets;
|
||||
|
||||
import org.testng.annotations.BeforeTest;
|
||||
import org.testng.annotations.Test;
|
||||
@ -101,7 +103,7 @@ public class ModuleReaderTest {
|
||||
/**
|
||||
* Test ModuleReader to module in runtime image
|
||||
*/
|
||||
public void testImage() throws Exception {
|
||||
public void testImage() throws IOException {
|
||||
|
||||
ModuleFinder finder = ModuleFinder.ofSystem();
|
||||
ModuleReference mref = finder.find(BASE_MODULE).get();
|
||||
@ -119,6 +121,7 @@ public class ModuleReaderTest {
|
||||
testFind(reader, name, expectedBytes);
|
||||
testOpen(reader, name, expectedBytes);
|
||||
testRead(reader, name, expectedBytes);
|
||||
testList(reader, name);
|
||||
|
||||
}
|
||||
|
||||
@ -168,7 +171,7 @@ public class ModuleReaderTest {
|
||||
/**
|
||||
* Test ModuleReader to exploded module
|
||||
*/
|
||||
public void testExplodedModule() throws Exception {
|
||||
public void testExplodedModule() throws IOException {
|
||||
test(MODS_DIR);
|
||||
}
|
||||
|
||||
@ -176,7 +179,7 @@ public class ModuleReaderTest {
|
||||
/**
|
||||
* Test ModuleReader to modular JAR
|
||||
*/
|
||||
public void testModularJar() throws Exception {
|
||||
public void testModularJar() throws IOException {
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mlib");
|
||||
|
||||
// jar cf mlib/${TESTMODULE}.jar -C mods .
|
||||
@ -190,7 +193,7 @@ public class ModuleReaderTest {
|
||||
/**
|
||||
* Test ModuleReader to JMOD
|
||||
*/
|
||||
public void testJMod() throws Exception {
|
||||
public void testJMod() throws IOException {
|
||||
Path dir = Files.createTempDirectory(USER_DIR, "mlib");
|
||||
|
||||
// jmod create --class-path mods/${TESTMODULE} mlib/${TESTMODULE}.jmod
|
||||
@ -211,13 +214,10 @@ public class ModuleReaderTest {
|
||||
* The test module is found on the given module path. Open a ModuleReader
|
||||
* to the test module and test the reader.
|
||||
*/
|
||||
void test(Path mp) throws Exception {
|
||||
void test(Path mp) throws IOException {
|
||||
|
||||
ModuleFinder finder = ModuleFinder.of(mp);
|
||||
if (finder instanceof ConfigurableModuleFinder) {
|
||||
// need ModuleFinder to be in the phase to find JMOD files
|
||||
((ConfigurableModuleFinder)finder).configurePhase(Phase.LINK_TIME);
|
||||
}
|
||||
ModuleFinder finder = SharedSecrets.getJavaLangModuleAccess()
|
||||
.newModulePath(Runtime.version(), true, mp);
|
||||
|
||||
ModuleReference mref = finder.find(TEST_MODULE).get();
|
||||
ModuleReader reader = mref.open();
|
||||
@ -234,6 +234,7 @@ public class ModuleReaderTest {
|
||||
testFind(reader, name, expectedBytes);
|
||||
testOpen(reader, name, expectedBytes);
|
||||
testRead(reader, name, expectedBytes);
|
||||
testList(reader, name);
|
||||
}
|
||||
|
||||
// test "not found"
|
||||
@ -275,13 +276,18 @@ public class ModuleReaderTest {
|
||||
reader.read(TEST_RESOURCES[0]);
|
||||
assertTrue(false);
|
||||
} catch (IOException expected) { }
|
||||
|
||||
try {
|
||||
reader.list();
|
||||
assertTrue(false);
|
||||
} catch (IOException expected) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ModuleReader#find
|
||||
*/
|
||||
void testFind(ModuleReader reader, String name, byte[] expectedBytes)
|
||||
throws Exception
|
||||
throws IOException
|
||||
{
|
||||
Optional<URI> ouri = reader.find(name);
|
||||
assertTrue(ouri.isPresent());
|
||||
@ -301,7 +307,7 @@ public class ModuleReaderTest {
|
||||
* Test ModuleReader#open
|
||||
*/
|
||||
void testOpen(ModuleReader reader, String name, byte[] expectedBytes)
|
||||
throws Exception
|
||||
throws IOException
|
||||
{
|
||||
Optional<InputStream> oin = reader.open(name);
|
||||
assertTrue(oin.isPresent());
|
||||
@ -317,7 +323,7 @@ public class ModuleReaderTest {
|
||||
* Test ModuleReader#read
|
||||
*/
|
||||
void testRead(ModuleReader reader, String name, byte[] expectedBytes)
|
||||
throws Exception
|
||||
throws IOException
|
||||
{
|
||||
Optional<ByteBuffer> obb = reader.read(name);
|
||||
assertTrue(obb.isPresent());
|
||||
@ -334,4 +340,24 @@ public class ModuleReaderTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ModuleReader#list
|
||||
*/
|
||||
void testList(ModuleReader reader, String name) throws IOException {
|
||||
List<String> list = reader.list().collect(Collectors.toList());
|
||||
Set<String> names = new HashSet<>(list);
|
||||
assertTrue(names.size() == list.size()); // no duplicates
|
||||
|
||||
assertTrue(names.contains("module-info.class"));
|
||||
assertTrue(names.contains(name));
|
||||
|
||||
// all resources should be locatable via find
|
||||
for (String e : names) {
|
||||
assertTrue(reader.find(e).isPresent());
|
||||
}
|
||||
|
||||
// should not contain directories
|
||||
names.forEach(e -> assertFalse(e.endsWith("/")));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,216 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @library /lib/testlibrary
|
||||
* @modules java.base/jdk.internal.module
|
||||
* @build MultiReleaseJarTest JarUtils
|
||||
* @run testng MultiReleaseJarTest
|
||||
* @run testng/othervm -Djdk.util.jar.enableMultiRelease=false MultiReleaseJarTest
|
||||
* @summary Basic test of ModuleReader with a modular JAR that is also a
|
||||
* multi-release JAR
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.lang.module.ModuleFinder;
|
||||
import java.lang.module.ModuleReader;
|
||||
import java.lang.module.ModuleReference;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jdk.internal.module.ModuleInfoWriter;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* Exercises ModuleReader with a modular JAR containing the following files:
|
||||
*
|
||||
* <pre>{@code
|
||||
* module-info.class
|
||||
* META-INF/versions/<version>/module.class
|
||||
* }</pre>
|
||||
*
|
||||
* The module-info.class in the top-level directory is the binary form of:
|
||||
* <pre>{@code
|
||||
* module jdk.test {
|
||||
* requires java.base;
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* The module-info.class in the versioned section is the binary form of:
|
||||
* <pre>{@code
|
||||
* module jdk.test {
|
||||
* requires java.base;
|
||||
* requires jdk.unsupported;
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
|
||||
@Test
|
||||
public class MultiReleaseJarTest {
|
||||
|
||||
// Java SE/JDK major release
|
||||
private static final int RELEASE = Runtime.version().major();
|
||||
|
||||
// the name of the test module
|
||||
private static final String MODULE_NAME = "jdk.test";
|
||||
|
||||
private static final String MODULE_INFO_CLASS = "module-info.class";
|
||||
|
||||
/**
|
||||
* Uses the ModuleFinder API to locate the module packaged as a modular
|
||||
* and mutli-release JAR and then creates a ModuleReader to access the
|
||||
* contents of the module.
|
||||
*/
|
||||
public void testMultiReleaseJar() throws IOException {
|
||||
|
||||
// are multi-release JARs enabled?
|
||||
String s = System.getProperty("jdk.util.jar.enableMultiRelease");
|
||||
boolean multiRelease = (s == null || Boolean.parseBoolean(s));
|
||||
|
||||
// create the multi-release modular JAR
|
||||
Path jarfile = createJarFile();
|
||||
|
||||
// find the module
|
||||
ModuleFinder finder = ModuleFinder.of(jarfile);
|
||||
Optional<ModuleReference> omref = finder.find(MODULE_NAME);
|
||||
assertTrue((omref.isPresent()));
|
||||
ModuleReference mref = omref.get();
|
||||
|
||||
// test that correct module-info.class was read
|
||||
checkDescriptor(mref.descriptor(), multiRelease);
|
||||
|
||||
// test ModuleReader
|
||||
try (ModuleReader reader = mref.open()) {
|
||||
|
||||
// open resource
|
||||
Optional<InputStream> oin = reader.open(MODULE_INFO_CLASS);
|
||||
assertTrue(oin.isPresent());
|
||||
try (InputStream in = oin.get()) {
|
||||
checkDescriptor(ModuleDescriptor.read(in), multiRelease);
|
||||
}
|
||||
|
||||
// read resource
|
||||
Optional<ByteBuffer> obb = reader.read(MODULE_INFO_CLASS);
|
||||
assertTrue(obb.isPresent());
|
||||
ByteBuffer bb = obb.get();
|
||||
try {
|
||||
checkDescriptor(ModuleDescriptor.read(bb), multiRelease);
|
||||
} finally {
|
||||
reader.release(bb);
|
||||
}
|
||||
|
||||
// find resource
|
||||
Optional<URI> ouri = reader.find(MODULE_INFO_CLASS);
|
||||
assertTrue(ouri.isPresent());
|
||||
URI uri = ouri.get();
|
||||
|
||||
String expectedTail = "!/";
|
||||
if (multiRelease)
|
||||
expectedTail += "META-INF/versions/" + RELEASE + "/";
|
||||
expectedTail += MODULE_INFO_CLASS;
|
||||
assertTrue(uri.toString().endsWith(expectedTail));
|
||||
|
||||
URLConnection uc = uri.toURL().openConnection();
|
||||
uc.setUseCaches(false);
|
||||
try (InputStream in = uc.getInputStream()) {
|
||||
checkDescriptor(ModuleDescriptor.read(in), multiRelease);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the module descriptor is the expected module descriptor.
|
||||
* When the multi release JAR feature is enabled then the module
|
||||
* descriptor is expected to have been read from the versioned section
|
||||
* of the JAR file.
|
||||
*/
|
||||
private void checkDescriptor(ModuleDescriptor descriptor, boolean multiRelease) {
|
||||
Set<String> requires = descriptor.requires().stream()
|
||||
.map(ModuleDescriptor.Requires::name)
|
||||
.collect(Collectors.toSet());
|
||||
assertTrue(requires.contains("java.base"));
|
||||
assertTrue(requires.contains("jdk.unsupported") == multiRelease);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the modular JAR for the test, returning the Path to the JAR file.
|
||||
*/
|
||||
private Path createJarFile() throws IOException {
|
||||
|
||||
// module descriptor for top-level directory
|
||||
ModuleDescriptor descriptor1
|
||||
= new ModuleDescriptor.Builder(MODULE_NAME)
|
||||
.requires("java.base")
|
||||
.build();
|
||||
|
||||
// module descriptor for versioned section
|
||||
ModuleDescriptor descriptor2
|
||||
= new ModuleDescriptor.Builder(MODULE_NAME)
|
||||
.requires("java.base")
|
||||
.requires("jdk.unsupported")
|
||||
.build();
|
||||
|
||||
Path top = Paths.get(MODULE_NAME);
|
||||
Files.createDirectories(top);
|
||||
|
||||
Path mi1 = Paths.get(MODULE_INFO_CLASS);
|
||||
try (OutputStream out = Files.newOutputStream(top.resolve(mi1))) {
|
||||
ModuleInfoWriter.write(descriptor1, out);
|
||||
}
|
||||
|
||||
Path vdir = Paths.get("META-INF", "versions", Integer.toString(RELEASE));
|
||||
Files.createDirectories(top.resolve(vdir));
|
||||
|
||||
Path mi2 = vdir.resolve(MODULE_INFO_CLASS);
|
||||
try (OutputStream out = Files.newOutputStream(top.resolve(mi2))) {
|
||||
ModuleInfoWriter.write(descriptor2, out);
|
||||
}
|
||||
|
||||
Manifest man = new Manifest();
|
||||
Attributes attrs = man.getMainAttributes();
|
||||
attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
|
||||
attrs.put(Attributes.Name.MULTI_RELEASE, "true");
|
||||
|
||||
Path jarfile = Paths.get(MODULE_NAME + ".jar");
|
||||
JarUtils.createJarFile(jarfile, man, top, mi1, mi2);
|
||||
|
||||
return jarfile;
|
||||
}
|
||||
}
|
336
jdk/test/java/lang/module/MultiReleaseJarTest.java
Normal file
336
jdk/test/java/lang/module/MultiReleaseJarTest.java
Normal file
@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @library /lib/testlibrary
|
||||
* @modules java.base/jdk.internal.module
|
||||
* @build MultiReleaseJarTest JarUtils
|
||||
* @run testng MultiReleaseJarTest
|
||||
* @run testng/othervm -Djdk.util.jar.enableMultiRelease=false MultiReleaseJarTest
|
||||
* @summary Basic test of modular JARs as multi-release JARs
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.lang.module.ModuleFinder;
|
||||
import java.lang.module.ModuleReader;
|
||||
import java.lang.module.ModuleReference;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import jdk.internal.module.ModuleInfoWriter;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
|
||||
@Test
|
||||
public class MultiReleaseJarTest {
|
||||
|
||||
private static final String MODULE_INFO = "module-info.class";
|
||||
|
||||
private static final int RELEASE = Runtime.version().major();
|
||||
|
||||
// are multi-release JARs enabled?
|
||||
private static final boolean MULTI_RELEASE;
|
||||
static {
|
||||
String s = System.getProperty("jdk.util.jar.enableMultiRelease");
|
||||
MULTI_RELEASE = (s == null || Boolean.parseBoolean(s));
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic test of a multi-release JAR.
|
||||
*/
|
||||
public void testBasic() throws Exception {
|
||||
String name = "m1";
|
||||
|
||||
ModuleDescriptor descriptor = new ModuleDescriptor.Builder(name)
|
||||
.requires("java.base")
|
||||
.build();
|
||||
|
||||
Path jar = new JarBuilder(name)
|
||||
.moduleInfo("module-info.class", descriptor)
|
||||
.resource("p/Main.class")
|
||||
.resource("p/Helper.class")
|
||||
.resource("META-INF/versions/9/p/Helper.class")
|
||||
.resource("META-INF/versions/9/p/internal/Helper9.class")
|
||||
.build();
|
||||
|
||||
// find the module
|
||||
ModuleFinder finder = ModuleFinder.of(jar);
|
||||
Optional<ModuleReference> omref = finder.find(name);
|
||||
assertTrue((omref.isPresent()));
|
||||
ModuleReference mref = omref.get();
|
||||
|
||||
// check module packages
|
||||
descriptor = mref.descriptor();
|
||||
Set<String> packages = descriptor.packages();
|
||||
assertTrue(packages.contains("p"));
|
||||
if (MULTI_RELEASE) {
|
||||
assertTrue(packages.size() == 2);
|
||||
assertTrue(packages.contains("p.internal"));
|
||||
} else {
|
||||
assertTrue(packages.size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a multi-release JAR with a module-info.class in the versioned
|
||||
* section of the JAR.
|
||||
*/
|
||||
public void testModuleInfoInVersionedSection() throws Exception {
|
||||
String name = "m1";
|
||||
|
||||
ModuleDescriptor descriptor1 = new ModuleDescriptor.Builder(name)
|
||||
.requires("java.base")
|
||||
.build();
|
||||
|
||||
// module descriptor for versioned section
|
||||
ModuleDescriptor descriptor2 = new ModuleDescriptor.Builder(name)
|
||||
.requires("java.base")
|
||||
.requires("jdk.unsupported")
|
||||
.build();
|
||||
|
||||
Path jar = new JarBuilder(name)
|
||||
.moduleInfo(MODULE_INFO, descriptor1)
|
||||
.resource("p/Main.class")
|
||||
.resource("p/Helper.class")
|
||||
.moduleInfo("META-INF/versions/9/" + MODULE_INFO, descriptor2)
|
||||
.resource("META-INF/versions/9/p/Helper.class")
|
||||
.resource("META-INF/versions/9/p/internal/Helper9.class")
|
||||
.build();
|
||||
|
||||
// find the module
|
||||
ModuleFinder finder = ModuleFinder.of(jar);
|
||||
Optional<ModuleReference> omref = finder.find(name);
|
||||
assertTrue((omref.isPresent()));
|
||||
ModuleReference mref = omref.get();
|
||||
|
||||
// ensure that the right module-info.class is loaded
|
||||
ModuleDescriptor descriptor = mref.descriptor();
|
||||
assertEquals(descriptor.name(), name);
|
||||
if (MULTI_RELEASE) {
|
||||
assertEquals(descriptor.requires(), descriptor2.requires());
|
||||
} else {
|
||||
assertEquals(descriptor.requires(), descriptor1.requires());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test multi-release JAR as an automatic module.
|
||||
*/
|
||||
public void testAutomaticModule() throws Exception {
|
||||
String name = "m";
|
||||
|
||||
Path jar = new JarBuilder(name)
|
||||
.resource("p/Main.class")
|
||||
.resource("p/Helper.class")
|
||||
.resource("META-INF/versions/9/p/Helper.class")
|
||||
.resource("META-INF/versions/9/p/internal/Helper9.class")
|
||||
.build();
|
||||
|
||||
// find the module
|
||||
ModuleFinder finder = ModuleFinder.of(jar);
|
||||
Optional<ModuleReference> omref = finder.find(name);
|
||||
assertTrue((omref.isPresent()));
|
||||
ModuleReference mref = omref.get();
|
||||
|
||||
// check module packages
|
||||
ModuleDescriptor descriptor = mref.descriptor();
|
||||
Set<String> packages = descriptor.packages();
|
||||
if (MULTI_RELEASE) {
|
||||
assertTrue(packages.size() == 2);
|
||||
assertTrue(packages.contains("p.internal"));
|
||||
} else {
|
||||
assertTrue(packages.size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exercise ModuleReader on a multi-release JAR
|
||||
*/
|
||||
public void testModuleReader() throws Exception {
|
||||
String name = "m1";
|
||||
|
||||
ModuleDescriptor descriptor1 = new ModuleDescriptor.Builder(name)
|
||||
.requires("java.base")
|
||||
.build();
|
||||
|
||||
// module descriptor for versioned section
|
||||
ModuleDescriptor descriptor2 = new ModuleDescriptor.Builder(name)
|
||||
.requires("java.base")
|
||||
.requires("jdk.unsupported")
|
||||
.build();
|
||||
|
||||
Path jar = new JarBuilder(name)
|
||||
.moduleInfo(MODULE_INFO, descriptor1)
|
||||
.moduleInfo("META-INF/versions/9/" + MODULE_INFO, descriptor2)
|
||||
.build();
|
||||
|
||||
// find the module
|
||||
ModuleFinder finder = ModuleFinder.of(jar);
|
||||
Optional<ModuleReference> omref = finder.find(name);
|
||||
assertTrue((omref.isPresent()));
|
||||
ModuleReference mref = omref.get();
|
||||
|
||||
ModuleDescriptor expected;
|
||||
if (MULTI_RELEASE) {
|
||||
expected = descriptor2;
|
||||
} else {
|
||||
expected = descriptor1;
|
||||
}
|
||||
|
||||
// test ModuleReader by reading module-info.class resource
|
||||
try (ModuleReader reader = mref.open()) {
|
||||
|
||||
// open resource
|
||||
Optional<InputStream> oin = reader.open(MODULE_INFO);
|
||||
assertTrue(oin.isPresent());
|
||||
try (InputStream in = oin.get()) {
|
||||
checkRequires(ModuleDescriptor.read(in), expected);
|
||||
}
|
||||
|
||||
// read resource
|
||||
Optional<ByteBuffer> obb = reader.read(MODULE_INFO);
|
||||
assertTrue(obb.isPresent());
|
||||
ByteBuffer bb = obb.get();
|
||||
try {
|
||||
checkRequires(ModuleDescriptor.read(bb), expected);
|
||||
} finally {
|
||||
reader.release(bb);
|
||||
}
|
||||
|
||||
// find resource
|
||||
Optional<URI> ouri = reader.find(MODULE_INFO);
|
||||
assertTrue(ouri.isPresent());
|
||||
URI uri = ouri.get();
|
||||
|
||||
String expectedTail = "!/";
|
||||
if (MULTI_RELEASE)
|
||||
expectedTail += "META-INF/versions/" + RELEASE + "/";
|
||||
expectedTail += MODULE_INFO;
|
||||
assertTrue(uri.toString().endsWith(expectedTail));
|
||||
|
||||
URLConnection uc = uri.toURL().openConnection();
|
||||
uc.setUseCaches(false);
|
||||
try (InputStream in = uc.getInputStream()) {
|
||||
checkRequires(ModuleDescriptor.read(in), expected);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that two ModuleDescriptor have the same requires
|
||||
*/
|
||||
static void checkRequires(ModuleDescriptor md1, ModuleDescriptor md2) {
|
||||
assertEquals(md1.requires(), md2.requires());
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of multi-release JAR files.
|
||||
*/
|
||||
static class JarBuilder {
|
||||
private String name;
|
||||
private Set<String> resources = new HashSet<>();
|
||||
private Map<String, ModuleDescriptor> descriptors = new HashMap<>();
|
||||
|
||||
JarBuilder(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a module-info.class to the JAR file.
|
||||
*/
|
||||
JarBuilder moduleInfo(String name, ModuleDescriptor descriptor) {
|
||||
descriptors.put(name, descriptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dummy resource to the JAR file.
|
||||
*/
|
||||
JarBuilder resource(String name) {
|
||||
resources.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the multi-release JAR, returning its file path.
|
||||
*/
|
||||
Path build() throws Exception {
|
||||
Path dir = Files.createTempDirectory(Paths.get(""), "jar");
|
||||
List<Path> files = new ArrayList<>();
|
||||
|
||||
// write the module-info.class
|
||||
for (Map.Entry<String, ModuleDescriptor> e : descriptors.entrySet()) {
|
||||
String name = e.getKey();
|
||||
ModuleDescriptor descriptor = e.getValue();
|
||||
Path mi = Paths.get(name.replace('/', File.separatorChar));
|
||||
Path parent = dir.resolve(mi).getParent();
|
||||
if (parent != null)
|
||||
Files.createDirectories(parent);
|
||||
try (OutputStream out = Files.newOutputStream(dir.resolve(mi))) {
|
||||
ModuleInfoWriter.write(descriptor, out);
|
||||
}
|
||||
files.add(mi);
|
||||
}
|
||||
|
||||
// write the dummy resources
|
||||
for (String name : resources) {
|
||||
Path file = Paths.get(name.replace('/', File.separatorChar));
|
||||
// create dummy resource
|
||||
Path parent = dir.resolve(file).getParent();
|
||||
if (parent != null)
|
||||
Files.createDirectories(parent);
|
||||
Files.createFile(dir.resolve(file));
|
||||
files.add(file);
|
||||
}
|
||||
|
||||
Manifest man = new Manifest();
|
||||
Attributes attrs = man.getMainAttributes();
|
||||
attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
|
||||
attrs.put(Attributes.Name.MULTI_RELEASE, "true");
|
||||
|
||||
Path jarfile = Paths.get(name + ".jar");
|
||||
JarUtils.createJarFile(jarfile, man, dir, files.toArray(new Path[0]));
|
||||
return jarfile;
|
||||
}
|
||||
}
|
||||
}
|
@ -62,9 +62,7 @@ public class MyResourcesProvider extends AbstractResourceBundleProvider {
|
||||
|
||||
@Override
|
||||
protected String toBundleName(String baseName, Locale locale) {
|
||||
// The resource bundle for Locale.JAPAN is loccated at jdk.test.resources
|
||||
// in module "asiabundles".
|
||||
String name = locale.equals(Locale.JAPAN) ? baseName : addRegion(baseName);
|
||||
String name = addRegion(baseName);
|
||||
return Control.getControl(Control.FORMAT_DEFAULT).toBundleName(name, locale);
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,8 @@
|
||||
* @summary Test the recording and checking of module hashes
|
||||
* @author Andrei Eremeev
|
||||
* @library /lib/testlibrary
|
||||
* @modules java.base/jdk.internal.module
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* java.base/jdk.internal.module
|
||||
* jdk.jlink
|
||||
* jdk.compiler
|
||||
* @build CompilerUtils
|
||||
@ -39,7 +40,6 @@ import java.lang.module.ModuleDescriptor;
|
||||
import java.lang.module.ModuleFinder;
|
||||
import java.lang.module.ModuleReader;
|
||||
import java.lang.module.ModuleReference;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@ -55,8 +55,10 @@ import java.util.Set;
|
||||
import java.util.spi.ToolProvider;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jdk.internal.module.ConfigurableModuleFinder;
|
||||
import jdk.internal.misc.SharedSecrets;
|
||||
import jdk.internal.misc.JavaLangModuleAccess;
|
||||
import jdk.internal.module.ModuleHashes;
|
||||
|
||||
import org.testng.annotations.BeforeTest;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@ -74,7 +76,6 @@ public class HashesTest {
|
||||
private final Path jmods = Paths.get("jmods");
|
||||
private final String[] modules = new String[] { "m1", "m2", "m3"};
|
||||
|
||||
private static Method hashesMethod;
|
||||
@BeforeTest
|
||||
private void setup() throws Exception {
|
||||
if (Files.exists(jmods)) {
|
||||
@ -97,13 +98,6 @@ public class HashesTest {
|
||||
// compile org.bar and org.foo
|
||||
compileModule("org.bar", modSrc);
|
||||
compileModule("org.foo", modSrc);
|
||||
|
||||
try {
|
||||
hashesMethod = ModuleDescriptor.class.getDeclaredMethod("hashes");
|
||||
hashesMethod.setAccessible(true);
|
||||
} catch (ReflectiveOperationException x) {
|
||||
throw new InternalError(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -143,17 +137,14 @@ public class HashesTest {
|
||||
}
|
||||
|
||||
private Optional<ModuleHashes> hashes(String name) throws Exception {
|
||||
ModuleFinder finder = ModuleFinder.of(jmods.resolve(name + ".jmod"));
|
||||
if (finder instanceof ConfigurableModuleFinder) {
|
||||
((ConfigurableModuleFinder) finder)
|
||||
.configurePhase(ConfigurableModuleFinder.Phase.LINK_TIME);
|
||||
}
|
||||
ModuleFinder finder = SharedSecrets.getJavaLangModuleAccess()
|
||||
.newModulePath(Runtime.version(), true, jmods.resolve(name + ".jmod"));
|
||||
ModuleReference mref = finder.find(name).orElseThrow(RuntimeException::new);
|
||||
ModuleReader reader = mref.open();
|
||||
try (InputStream in = reader.open("module-info.class").get()) {
|
||||
ModuleDescriptor md = ModuleDescriptor.read(in);
|
||||
Optional<ModuleHashes> hashes =
|
||||
(Optional<ModuleHashes>) hashesMethod.invoke(md);
|
||||
JavaLangModuleAccess jmla = SharedSecrets.getJavaLangModuleAccess();
|
||||
Optional<ModuleHashes> hashes = jmla.hashes(md);
|
||||
System.out.format("hashes in module %s %s%n", name,
|
||||
hashes.isPresent() ? "present" : "absent");
|
||||
if (hashes.isPresent()) {
|
||||
|
Loading…
Reference in New Issue
Block a user