--- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java Mon May 23 12:44:55 2016 -0700
+++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java Mon May 23 21:28:39 2016 +0100
@@ -38,6 +38,7 @@
import java.lang.module.ResolutionException;
import java.lang.module.ResolvedModule;
import java.net.URI;
+import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.Files;
import java.nio.file.Paths;
@@ -62,6 +63,7 @@
import static jdk.internal.util.jar.JarIndex.INDEX_NAME;
import static java.util.jar.JarFile.MANIFEST_NAME;
import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toSet;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
/**
@@ -129,10 +131,8 @@
private static final String MODULE_INFO = "module-info.class";
- Path moduleInfo;
- private boolean isModularJar() { return moduleInfo != null; }
-
static final String MANIFEST_DIR = "META-INF/";
+ static final String VERSIONS_DIR = MANIFEST_DIR + "versions/";
static final String VERSION = "1.0";
private static ResourceBundle rsrc;
@@ -242,12 +242,27 @@
addMainClass(manifest, ename);
}
}
- expand(null, files, false);
+ Map<String,Path> moduleInfoPaths = new HashMap<>();
+ expand(null, files, false, moduleInfoPaths);
+
+ Map<String,byte[]> moduleInfos = new LinkedHashMap<>();
+ if (!moduleInfoPaths.isEmpty()) {
+ if (!checkModuleInfos(moduleInfoPaths))
+ return false;
- byte[] moduleInfoBytes = null;
- if (isModularJar()) {
- moduleInfoBytes = addExtendedModuleAttributes(
- readModuleInfo(moduleInfo));
+ // root module-info first
+ byte[] b = readModuleInfo(moduleInfoPaths.get(MODULE_INFO));
+ moduleInfos.put(MODULE_INFO, b);
+ for (Map.Entry<String,Path> e : moduleInfoPaths.entrySet())
+ moduleInfos.putIfAbsent(e.getKey(), readModuleInfo(e.getValue()));
+
+ if (!addExtendedModuleAttributes(moduleInfos))
+ return false;
+
+ // Basic consistency checks for modular jars.
+ if (!checkServices(moduleInfos.get(MODULE_INFO)))
+ return false;
+
} else if (moduleVersion != null || modulesToHash != null) {
error(getMsg("error.module.options.without.info"));
return false;
@@ -274,13 +289,7 @@
tmpfile = createTemporaryFile(tmpbase, ".jar");
out = new FileOutputStream(tmpfile);
}
- create(new BufferedOutputStream(out, 4096), manifest, moduleInfoBytes);
-
- // Consistency checks for modular jars.
- if (isModularJar()) {
- if (!checkServices(moduleInfoBytes))
- return false;
- }
+ create(new BufferedOutputStream(out, 4096), manifest, moduleInfos);
if (in != null) {
in.close();
@@ -337,19 +346,20 @@
}
InputStream manifest = (!Mflag && (mname != null)) ?
(new FileInputStream(mname)) : null;
- expand(null, files, true);
+
+ Map<String,Path> moduleInfoPaths = new HashMap<>();
+ expand(null, files, true, moduleInfoPaths);
- byte[] moduleInfoBytes = null;
- if (isModularJar()) {
- moduleInfoBytes = readModuleInfo(moduleInfo);
- }
+ Map<String,byte[]> moduleInfos = new HashMap<>();
+ for (Map.Entry<String,Path> e : moduleInfoPaths.entrySet())
+ moduleInfos.put(e.getKey(), readModuleInfo(e.getValue()));
boolean updateOk = update(in, new BufferedOutputStream(out),
- manifest, moduleInfoBytes, null);
+ manifest, moduleInfos, null);
// Consistency checks for modular jars.
- if (isModularJar()) {
- if(!checkServices(moduleInfoBytes))
+ if (!moduleInfos.isEmpty()) {
+ if(!checkServices(moduleInfos.get(MODULE_INFO)))
return false;
}
@@ -638,7 +648,12 @@
* Expands list of files to process into full list of all files that
* can be found by recursively descending directories.
*/
- void expand(File dir, String[] files, boolean isUpdate) throws IOException {
+ void expand(File dir,
+ String[] files,
+ boolean isUpdate,
+ Map<String,Path> moduleInfoPaths)
+ throws IOException
+ {
if (files == null)
return;
@@ -651,18 +666,17 @@
if (f.isFile()) {
String path = f.getPath();
- if (entryName(path).equals(MODULE_INFO)) {
- if (moduleInfo != null && vflag)
- output(formatMsg("error.unexpected.module-info", path));
- moduleInfo = f.toPath();
+ String entryName = entryName(path);
+ if (entryName.endsWith(MODULE_INFO)) {
+ moduleInfoPaths.put(entryName, f.toPath());
if (isUpdate)
- entryMap.put(entryName(path), f);
+ entryMap.put(entryName, f);
} else if (entries.add(f)) {
- jarEntries.add(entryName(path));
- if (path.endsWith(".class"))
- packages.add(toPackageName(entryName(path)));
+ jarEntries.add(entryName);
+ if (path.endsWith(".class") && !entryName.startsWith(VERSIONS_DIR))
+ packages.add(toPackageName(entryName));
if (isUpdate)
- entryMap.put(entryName(f.getPath()), f);
+ entryMap.put(entryName, f);
}
} else if (f.isDirectory()) {
if (entries.add(f)) {
@@ -672,7 +686,7 @@
(dirPath + File.separator);
entryMap.put(entryName(dirPath), f);
}
- expand(f, f.list(), isUpdate);
+ expand(f, f.list(), isUpdate, moduleInfoPaths);
}
} else {
error(formatMsg("error.nosuch.fileordir", String.valueOf(f)));
@@ -684,7 +698,7 @@
/**
* Creates a new JAR file.
*/
- void create(OutputStream out, Manifest manifest, byte[] moduleInfoBytes)
+ void create(OutputStream out, Manifest manifest, Map<String,byte[]> moduleInfos)
throws IOException
{
ZipOutputStream zos = new JarOutputStream(out);
@@ -710,17 +724,19 @@
manifest.write(zos);
zos.closeEntry();
}
- if (moduleInfoBytes != null) {
+ for (Map.Entry<String,byte[]> mi : moduleInfos.entrySet()) {
+ String entryName = mi.getKey();
+ byte[] miBytes = mi.getValue();
if (vflag) {
- output(getMsg("out.added.module-info"));
+ output(formatMsg("out.added.module-info", entryName));
}
- ZipEntry e = new ZipEntry(MODULE_INFO);
+ ZipEntry e = new ZipEntry(mi.getKey());
e.setTime(System.currentTimeMillis());
if (flag0) {
- crc32ModuleInfo(e, moduleInfoBytes);
+ crc32ModuleInfo(e, miBytes);
}
zos.putNextEntry(e);
- ByteArrayInputStream in = new ByteArrayInputStream(moduleInfoBytes);
+ ByteArrayInputStream in = new ByteArrayInputStream(miBytes);
in.transferTo(zos);
zos.closeEntry();
}
@@ -755,18 +771,41 @@
}
/**
+ * Returns true of the given module-info's are located in acceptable
+ * locations. Otherwise, outputs an appropriate message and returns false.
+ */
+ private boolean checkModuleInfos(Map<String,?> moduleInfos) {
+ // there must always be, at least, a root module-info
+ if (!moduleInfos.containsKey(MODULE_INFO)) {
+ error(getMsg("error.versioned.info.without.root"));
+ return false;
+ }
+
+ // module-info can only appear in the root, or a versioned section
+ Optional<String> other = moduleInfos.keySet().stream()
+ .filter(x -> !x.equals(MODULE_INFO))
+ .filter(x -> !x.startsWith(VERSIONS_DIR))
+ .findFirst();
+
+ if (other.isPresent()) {
+ error(formatMsg("error.unexpected.module-info", other.get()));
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Updates an existing jar file.
*/
boolean update(InputStream in, OutputStream out,
InputStream newManifest,
- byte[] newModuleInfoBytes,
+ Map<String,byte[]> moduleInfos,
JarIndex jarIndex) throws IOException
{
ZipInputStream zis = new ZipInputStream(in);
ZipOutputStream zos = new JarOutputStream(out);
ZipEntry e = null;
boolean foundManifest = false;
- boolean foundModuleInfo = false;
boolean updateOk = true;
if (jarIndex != null) {
@@ -778,7 +817,7 @@
String name = e.getName();
boolean isManifestEntry = equalsIgnoreCase(name, MANIFEST_NAME);
- boolean isModuleInfoEntry = name.equals(MODULE_INFO);
+ boolean isModuleInfoEntry = name.endsWith(MODULE_INFO);
if ((jarIndex != null && equalsIgnoreCase(name, INDEX_NAME))
|| (Mflag && isManifestEntry)) {
@@ -806,13 +845,8 @@
if (!updateManifest(old, zos)) {
return false;
}
- } else if (isModuleInfoEntry
- && ((newModuleInfoBytes != null) || (ename != null)
- || moduleVersion != null || modulesToHash != null)) {
- if (newModuleInfoBytes == null) {
- // Update existing module-info.class
- newModuleInfoBytes = readModuleInfo(zis);
- }
+ } else if (moduleInfos != null && isModuleInfoEntry) {
+ moduleInfos.putIfAbsent(name, readModuleInfo(zis));
} else {
if (!entryMap.containsKey(name)) { // copy the old stuff
// do our own compression
@@ -860,13 +894,22 @@
}
}
- // write the module-info.class
- if (newModuleInfoBytes != null) {
- newModuleInfoBytes = addExtendedModuleAttributes(newModuleInfoBytes);
+ if (moduleInfos != null && !moduleInfos.isEmpty()) {
+ if (!checkModuleInfos(moduleInfos))
+ updateOk = false;
+
+ if (updateOk) {
+ if (!addExtendedModuleAttributes(moduleInfos))
+ updateOk = false;
+ }
// TODO: check manifest main classes, etc
- if (!updateModuleInfo(newModuleInfoBytes, zos)) {
- updateOk = false;
+
+ if (updateOk) {
+ for (Map.Entry<String,byte[]> mi : moduleInfos.entrySet()) {
+ if (!updateModuleInfo(mi.getValue(), zos, mi.getKey()))
+ updateOk = false;
+ }
}
} else if (moduleVersion != null || modulesToHash != null) {
error(getMsg("error.module.options.without.info"));
@@ -894,10 +937,10 @@
zos.closeEntry();
}
- private boolean updateModuleInfo(byte[] moduleInfoBytes, ZipOutputStream zos)
+ private boolean updateModuleInfo(byte[] moduleInfoBytes, ZipOutputStream zos, String entryName)
throws IOException
{
- ZipEntry e = new ZipEntry(MODULE_INFO);
+ ZipEntry e = new ZipEntry(entryName);
e.setTime(System.currentTimeMillis());
if (flag0) {
crc32ModuleInfo(e, moduleInfoBytes);
@@ -905,7 +948,7 @@
zos.putNextEntry(e);
zos.write(moduleInfoBytes);
if (vflag) {
- output(getMsg("out.update.module-info"));
+ output(formatMsg("out.update.module-info", entryName));
}
return true;
}
@@ -1710,13 +1753,11 @@
return (classname.replace('.', '/')) + ".class";
}
+ /* A module must have the implementation class of the services it 'provides'. */
private boolean checkServices(byte[] moduleInfoBytes)
throws IOException
{
- ModuleDescriptor md;
- try (InputStream in = new ByteArrayInputStream(moduleInfoBytes)) {
- md = ModuleDescriptor.read(in);
- }
+ ModuleDescriptor md = ModuleDescriptor.read(ByteBuffer.wrap(moduleInfoBytes));
Set<String> missing = md.provides()
.values()
.stream()
@@ -1732,63 +1773,140 @@
}
/**
- * Returns a byte array containing the module-info.class.
+ * Adds extended modules attributes to the given module-info's. The given
+ * Map values are updated in-place. Returns false if an error occurs.
+ */
+ private boolean addExtendedModuleAttributes(Map<String,byte[]> moduleInfos)
+ throws IOException
+ {
+ assert !moduleInfos.isEmpty() && moduleInfos.get(MODULE_INFO) != null;
+
+ ByteBuffer bb = ByteBuffer.wrap(moduleInfos.get(MODULE_INFO));
+ ModuleDescriptor rd = ModuleDescriptor.read(bb);
+
+ Set<String> exports = rd.exports()
+ .stream()
+ .map(Exports::source)
+ .collect(toSet());
+
+ Set<String> conceals = packages.stream()
+ .filter(p -> !exports.contains(p))
+ .collect(toSet());
+
+ for (Map.Entry<String,byte[]> e: moduleInfos.entrySet()) {
+ ModuleDescriptor vd = ModuleDescriptor.read(ByteBuffer.wrap(e.getValue()));
+ if (!(isValidVersionedDescriptor(vd, rd)))
+ return false;
+ e.setValue(extendedInfoBytes(rd, vd, e.getValue(), conceals));
+ }
+ return true;
+ }
+
+ private static boolean isPlatformModule(String name) {
+ return name.startsWith("java.") || name.startsWith("jdk.");
+ }
+
+ /**
+ * Tells whether or not the given versioned module descriptor's attributes
+ * are valid when compared against the given root module descriptor.
+ *
+ * A versioned module descriptor must be identical to the root module
+ * descriptor, with two exceptions:
+ * - A versioned descriptor can have different non-public `requires`
+ * clauses of platform ( `java.*` and `jdk.*` ) modules, and
+ * - A versioned descriptor can have different `uses` clauses, even of
+ * service types defined outside of the platform modules.
+ */
+ private boolean isValidVersionedDescriptor(ModuleDescriptor vd,
+ ModuleDescriptor rd)
+ throws IOException
+ {
+ if (!rd.name().equals(vd.name())) {
+ fatalError(getMsg("error.versioned.info.name.notequal"));
+ return false;
+ }
+ if (!rd.requires().equals(vd.requires())) {
+ Set<Requires> rootRequires = rd.requires();
+ for (Requires r : vd.requires()) {
+ if (rootRequires.contains(r)) {
+ continue;
+ } else if (r.modifiers().contains(Requires.Modifier.PUBLIC)) {
+ fatalError(getMsg("error.versioned.info.requires.public"));
+ return false;
+ } else if (!isPlatformModule(r.name())) {
+ fatalError(getMsg("error.versioned.info.requires.added"));
+ return false;
+ }
+ }
+ for (Requires r : rootRequires) {
+ Set<Requires> mdRequires = vd.requires();
+ if (mdRequires.contains(r)) {
+ continue;
+ } else if (!isPlatformModule(r.name())) {
+ fatalError(getMsg("error.versioned.info.requires.dropped"));
+ return false;
+ }
+ }
+ }
+ if (!rd.exports().equals(vd.exports())) {
+ fatalError(getMsg("error.versioned.info.exports.notequal"));
+ return false;
+ }
+ if (!rd.provides().equals(vd.provides())) {
+ fatalError(getMsg("error.versioned.info.provides.notequal"));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns a byte array containing the given module-info.class plus any
+ * extended attributes.
*
* If --module-version, --main-class, or other options were provided
* then the corresponding class file attributes are added to the
* module-info here.
*/
- private byte[] addExtendedModuleAttributes(byte[] moduleInfoBytes)
+ private byte[] extendedInfoBytes(ModuleDescriptor rootDescriptor,
+ ModuleDescriptor md,
+ byte[] miBytes,
+ Set<String> conceals)
throws IOException
{
- assert isModularJar();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InputStream is = new ByteArrayInputStream(miBytes);
+ ModuleInfoExtender extender = ModuleInfoExtender.newExtender(is);
- ModuleDescriptor md;
- try (InputStream in = new ByteArrayInputStream(moduleInfoBytes)) {
- md = ModuleDescriptor.read(in);
- }
- String name = md.name();
- Set<String> exported = md.exports()
- .stream()
- .map(ModuleDescriptor.Exports::source)
- .collect(Collectors.toSet());
+ // Add (or replace) the ConcealedPackages attribute
+ extender.conceals(conceals);
- // copy the module-info.class into the jmod with the additional
- // attributes for the version, main class and other meta data
- try (InputStream in = new ByteArrayInputStream(moduleInfoBytes);
- ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
- ModuleInfoExtender extender = ModuleInfoExtender.newExtender(in);
-
- // Add (or replace) the ConcealedPackages attribute
- Set<String> conceals = packages.stream()
- .filter(p -> !exported.contains(p))
- .collect(Collectors.toSet());
+ // --main-class
+ if (ename != null)
+ extender.mainClass(ename);
+ else if (rootDescriptor.mainClass().isPresent())
+ extender.mainClass(rootDescriptor.mainClass().get());
- extender.conceals(conceals);
-
- // --main-class
- if (ename != null)
- extender.mainClass(ename);
-
- // --module-version
- if (moduleVersion != null)
- extender.version(moduleVersion);
+ // --module-version
+ if (moduleVersion != null)
+ extender.version(moduleVersion);
+ else if (rootDescriptor.version().isPresent())
+ extender.version(rootDescriptor.version().get());
- // --hash-modules
- if (modulesToHash != null) {
- Hasher hasher = new Hasher(md, fname);
- ModuleHashes moduleHashes = hasher.computeHashes(name);
- if (moduleHashes != null) {
- extender.hashes(moduleHashes);
- } else {
- // should it issue warning or silent?
- System.out.println("warning: no module is recorded in hash in " + name);
- }
+ // --hash-modules
+ if (modulesToHash != null) {
+ String mn = md.name();
+ Hasher hasher = new Hasher(md, fname);
+ ModuleHashes moduleHashes = hasher.computeHashes(mn);
+ if (moduleHashes != null) {
+ extender.hashes(moduleHashes);
+ } else {
+ // should it issue warning or silent?
+ System.out.println("warning: no module is recorded in hash in " + mn);
}
+ }
- extender.write(baos);
- return baos.toByteArray();
- }
+ extender.write(baos);
+ return baos.toByteArray();
}
/**
@@ -1865,8 +1983,8 @@
deque.add(name);
Set<String> mods = visitNodes(graph, deque);
- // filter modules matching the pattern specified --hash-modules
- // as well as itself as the jmod file is being generated
+ // filter modules matching the pattern specified in --hash-modules,
+ // as well as the modular jar file that is being created / updated
Map<String, Path> modulesForHash = mods.stream()
.filter(mn -> !mn.equals(name) && modules.contains(mn))
.collect(Collectors.toMap(Function.identity(), moduleNameToPath::get));
--- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties Mon May 23 12:44:55 2016 -0700
+++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties Mon May 23 21:28:39 2016 +0100
@@ -62,16 +62,33 @@
Unexpected module descriptor {0}
error.module.descriptor.not.found=\
Module descriptor not found
+error.versioned.info.without.root=\
+ module-info.class found in versioned section without module-info.class \
+ in the root
+error.versioned.info.name.notequal=\
+ module-info.class in versioned section contains incorrect name
+error.versioned.info.requires.public=\
+ module-info.class in versioned section contains additional requires public
+error.versioned.info.requires.added=\
+ module-info.class in versioned section contains additional requires
+error.versioned.info.requires.dropped=\
+ module-info.class in versioned section contains missing requires
+error.versioned.info.exports.notequal=\
+ module-info.class in versioned section contains different exports
+error.versioned.info.provides.notequal=\
+ module-info.class in versioned section contains different provides
+error.invalid.versioned.module.attribute=\
+ Invalid module descriptor attribute {0}
error.missing.provider=\
Service provider not found: {0}
out.added.manifest=\
added manifest
out.added.module-info=\
- added module-info.class
+ added module-info: {0}
out.update.manifest=\
updated manifest
out.update.module-info=\
- updated module-info.class
+ updated module-info: {0}
out.ignore.entry=\
ignoring entry {0}
out.adding=\
--- a/jdk/test/tools/jar/modularJar/Basic.java Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/Basic.java Mon May 23 21:28:39 2016 +0100
@@ -33,6 +33,7 @@
import java.util.function.Consumer;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import javax.tools.JavaCompiler;
@@ -52,45 +53,64 @@
/*
* @test
* @library /lib/testlibrary
+ * @modules jdk.compiler
+ * jdk.jartool
* @build jdk.testlibrary.FileUtils jdk.testlibrary.JDKToolFinder
* @compile Basic.java
* @run testng Basic
- * @summary Basic test for Modular jars
+ * @summary Tests for plain Modular jars & Multi-Release Modular jars
*/
public class Basic {
static final Path TEST_SRC = Paths.get(System.getProperty("test.src", "."));
static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", "."));
static final Path MODULE_CLASSES = TEST_CLASSES.resolve("build");
+ static final Path MRJAR_DIR = MODULE_CLASSES.resolve("mrjar");
// Details based on the checked in module source
static TestModuleData FOO = new TestModuleData("foo",
"1.123",
"jdk.test.foo.Foo",
- "Hello World!!!", null,
- "jdk.test.foo.internal");
+ "Hello World!!!",
+ null, // no hashes
+ Set.of("java.base"),
+ Set.of("jdk.test.foo"),
+ null, // no uses
+ null, // no provides
+ Set.of("jdk.test.foo.internal"));
static TestModuleData BAR = new TestModuleData("bar",
"4.5.6.7",
"jdk.test.bar.Bar",
- "Hello from Bar!", null,
- "jdk.test.bar",
- "jdk.test.bar.internal");
+ "Hello from Bar!",
+ null, // no hashes
+ Set.of("java.base", "foo"),
+ null, // no exports
+ null, // no uses
+ null, // no provides
+ Set.of("jdk.test.bar",
+ "jdk.test.bar.internal"));
static class TestModuleData {
final String moduleName;
+ final Set<String> requires;
+ final Set<String> exports;
+ final Set<String> uses;
+ final Set<String> provides;
final String mainClass;
final String version;
final String message;
final String hashes;
final Set<String> conceals;
- TestModuleData(String mn, String v, String mc, String m, String h, String... pkgs) {
+
+ TestModuleData(String mn, String v, String mc, String m, String h,
+ Set<String> requires, Set<String> exports, Set<String> uses,
+ Set<String> provides, Set<String> conceals) {
moduleName = mn; mainClass = mc; version = v; message = m; hashes = h;
- conceals = new HashSet<>();
- Stream.of(pkgs).forEach(conceals::add);
- }
- TestModuleData(String mn, String v, String mc, String m, String h, Set<String> pkgs) {
- moduleName = mn; mainClass = mc; version = v; message = m; hashes = h;
- conceals = pkgs;
+ this.requires = requires;
+ this.exports = exports;
+ this.uses = uses;
+ this.provides = provides;
+ this.conceals = conceals;
}
static TestModuleData from(String s) {
try {
@@ -99,7 +119,8 @@
String message = null;
String name = null, version = null, mainClass = null;
String hashes = null;
- Set<String> conceals = null;
+ Set<String> requires, exports, uses, provides, conceals;
+ requires = exports = uses = provides = conceals = null;
while ((line = reader.readLine()) != null) {
if (line.startsWith("message:")) {
message = line.substring("message:".length());
@@ -114,28 +135,46 @@
}
} else if (line.startsWith("mainClass:")) {
mainClass = line.substring("mainClass:".length());
+ } else if (line.startsWith("requires:")) {
+ line = line.substring("requires:".length());
+ requires = stringToSet(line);
+ } else if (line.startsWith("exports:")) {
+ line = line.substring("exports:".length());
+ exports = stringToSet(line);
+ } else if (line.startsWith("uses:")) {
+ line = line.substring("uses:".length());
+ uses = stringToSet(line);
+ } else if (line.startsWith("provides:")) {
+ line = line.substring("provides:".length());
+ provides = stringToSet(line);
} else if (line.startsWith("hashes:")) {
hashes = line.substring("hashes:".length());
- } else if (line.startsWith("conceals:")) {
+ } else if (line.startsWith("conceals:")) {
line = line.substring("conceals:".length());
- conceals = new HashSet<>();
- int i = line.indexOf(',');
- if (i != -1) {
- String[] p = line.split(",");
- Stream.of(p).forEach(conceals::add);
- } else {
- conceals.add(line);
- }
+ conceals = stringToSet(line);
} else {
throw new AssertionError("Unknown value " + line);
}
}
- return new TestModuleData(name, version, mainClass, message, hashes, conceals);
+ return new TestModuleData(name, version, mainClass, message,
+ hashes, requires, exports, uses,
+ provides, conceals);
} catch (IOException x) {
throw new UncheckedIOException(x);
}
}
+ static Set<String> stringToSet(String commaList) {
+ Set<String> s = new HashSet<>();
+ int i = commaList.indexOf(',');
+ if (i != -1) {
+ String[] p = commaList.split(",");
+ Stream.of(p).forEach(s::add);
+ } else {
+ s.add(commaList);
+ }
+ return s;
+ }
}
static void assertModuleData(Result r, TestModuleData expected) {
@@ -150,10 +189,19 @@
"Expected version: ", expected.version, ", got:", received.version);
assertTrue(expected.mainClass.equals(received.mainClass),
"Expected mainClass: ", expected.mainClass, ", got:", received.mainClass);
- expected.conceals.forEach(p -> assertTrue(received.conceals.contains(p),
- "Expected ", p, ", in ", received.conceals));
- received.conceals.forEach(p -> assertTrue(expected.conceals.contains(p),
- "Expected ", p, ", in ", expected.conceals));
+ assertSetsEqual(expected.requires, received.requires);
+ assertSetsEqual(expected.exports, received.exports);
+ assertSetsEqual(expected.uses, received.uses);
+ assertSetsEqual(expected.provides, received.provides);
+ assertSetsEqual(expected.conceals, received.conceals);
+ }
+
+ static void assertSetsEqual(Set<String> s1, Set<String> s2) {
+ if (s1 == null && s2 == null) // none expected, or received
+ return;
+ assertTrue(s1.size() == s2.size(),
+ "Unexpected set size difference: ", s1.size(), ", ", s2.size());
+ s1.forEach(p -> assertTrue(s2.contains(p), "Expected ", p, ", in ", s2));
}
@BeforeTest
@@ -161,6 +209,10 @@
compileModule(FOO.moduleName);
compileModule(BAR.moduleName, MODULE_CLASSES);
compileModule("baz"); // for service provider consistency checking
+
+ setupMRJARModuleInfo(FOO.moduleName);
+ setupMRJARModuleInfo(BAR.moduleName);
+ setupMRJARModuleInfo("baz");
}
@Test
@@ -180,7 +232,6 @@
java(mp, FOO.moduleName + "/" + FOO.mainClass)
.assertSuccess()
.resultChecker(r -> assertModuleData(r, FOO));
-
try (InputStream fis = Files.newInputStream(modularJar);
JarInputStream jis = new JarInputStream(fis)) {
assertTrue(!jarContains(jis, "./"),
@@ -188,6 +239,30 @@
}
}
+ /** Similar to createFoo, but with a Multi-Release Modular jar. */
+ @Test
+ public void createMRMJarFoo() throws IOException {
+ Path mp = Paths.get("createMRMJarFoo");
+ createTestDir(mp);
+ Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
+ Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName);
+ Path modularJar = mp.resolve(FOO.moduleName + ".jar");
+
+ // Positive test, create
+ jar("--create",
+ "--file=" + modularJar.toString(),
+ "--main-class=" + FOO.mainClass,
+ "--module-version=" + FOO.version,
+ "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
+ "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
+ "-C", modClasses.toString(), ".")
+ .assertSuccess();
+ java(mp, FOO.moduleName + "/" + FOO.mainClass)
+ .assertSuccess()
+ .resultChecker(r -> assertModuleData(r, FOO));
+ }
+
+
@Test
public void updateFoo() throws IOException {
Path mp = Paths.get("updateFoo");
@@ -213,6 +288,32 @@
}
@Test
+ public void updateMRMJarFoo() throws IOException {
+ Path mp = Paths.get("updateMRMJarFoo");
+ createTestDir(mp);
+ Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
+ Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName);
+ Path modularJar = mp.resolve(FOO.moduleName + ".jar");
+
+ jar("--create",
+ "--file=" + modularJar.toString(),
+ "--no-manifest",
+ "-C", modClasses.toString(), "jdk")
+ .assertSuccess();
+ jar("--update",
+ "--file=" + modularJar.toString(),
+ "--main-class=" + FOO.mainClass,
+ "--module-version=" + FOO.version,
+ "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
+ "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
+ "-C", modClasses.toString(), "module-info.class")
+ .assertSuccess();
+ java(mp, FOO.moduleName + "/" + FOO.mainClass)
+ .assertSuccess()
+ .resultChecker(r -> assertModuleData(r, FOO));
+ }
+
+ @Test
public void partialUpdateFooMainClass() throws IOException {
Path mp = Paths.get("partialUpdateFooMainClass");
createTestDir(mp);
@@ -290,6 +391,30 @@
}
@Test
+ public void partialUpdateMRMJarFooNotAllFiles() throws IOException {
+ Path mp = Paths.get("partialUpdateMRMJarFooNotAllFiles");
+ createTestDir(mp);
+ Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
+ Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName);
+ Path modularJar = mp.resolve(FOO.moduleName + ".jar");
+
+ jar("--create",
+ "--file=" + modularJar.toString(),
+ "--module-version=" + FOO.version,
+ "-C", modClasses.toString(), ".")
+ .assertSuccess();
+ jar("--update",
+ "--file=" + modularJar.toString(),
+ "--main-class=" + FOO.mainClass,
+ "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
+ "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class")
+ .assertSuccess();
+ java(mp, FOO.moduleName + "/" + FOO.mainClass)
+ .assertSuccess()
+ .resultChecker(r -> assertModuleData(r, FOO));
+ }
+
+ @Test
public void partialUpdateFooAllFilesAndAttributes() throws IOException {
Path mp = Paths.get("partialUpdateFooAllFilesAndAttributes");
createTestDir(mp);
@@ -528,6 +653,24 @@
}
@Test
+ public void servicesCreateWithoutFailureMRMJAR() throws IOException {
+ Path mp = Paths.get("servicesCreateWithoutFailureMRMJAR");
+ createTestDir(mp);
+ Path modClasses = MODULE_CLASSES.resolve("baz");
+ Path mrjarDir = MRJAR_DIR.resolve("baz");
+ Path modularJar = mp.resolve("baz" + ".jar");
+
+ jar("--create",
+ "--file=" + modularJar.toString(),
+ "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
+ "-C", modClasses.toString(), "module-info.class",
+ "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
+ "-C", modClasses.toString(), "jdk/test/baz/BazService.class",
+ "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class")
+ .assertSuccess();
+ }
+
+ @Test
public void printModuleDescriptorFoo() throws IOException {
Path mp = Paths.get("printModuleDescriptorFoo");
createTestDir(mp);
@@ -611,6 +754,24 @@
return build;
}
+ static void setupMRJARModuleInfo(String moduleName) throws IOException {
+ Path modClasses = MODULE_CLASSES.resolve(moduleName);
+ Path metaInfDir = MRJAR_DIR.resolve(moduleName).resolve("META-INF");
+ Path versionSection = metaInfDir.resolve("versions").resolve("9");
+ createTestDir(versionSection);
+
+ Path versionModuleInfo = versionSection.resolve("module-info.class");
+ System.out.println("copying " + modClasses.resolve("module-info.class") + " to " + versionModuleInfo);
+ Files.copy(modClasses.resolve("module-info.class"), versionModuleInfo);
+
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
+ manifest.getMainAttributes().putValue("Multi-Release", "true");
+ try (OutputStream os = Files.newOutputStream(metaInfDir.resolve("MANIFEST.MF"))) {
+ manifest.write(os);
+ }
+ }
+
// Re-enable when there is support in javax.tools for module path
// static void javac(Path dest, Path... sourceFiles) throws IOException {
// out.printf("Compiling %d source files %s%n", sourceFiles.length,
@@ -690,7 +851,7 @@
static void createTestDir(Path p) throws IOException{
if (Files.exists(p))
FileUtils.deleteFileTreeWithRetry(p);
- Files.createDirectory(p);
+ Files.createDirectories(p);
}
static boolean jarContains(JarInputStream jis, String entryName)
--- a/jdk/test/tools/jar/modularJar/src/bar/jdk/test/bar/Bar.java Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/bar/jdk/test/bar/Bar.java Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
+ * 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
@@ -26,6 +24,8 @@
package jdk.test.bar;
import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleDescriptor.Exports;
+import java.lang.module.ModuleDescriptor.Requires;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.StringJoiner;
@@ -39,18 +39,36 @@
ModuleDescriptor md = Bar.class.getModule().getDescriptor();
System.out.println("nameAndVersion:" + md.toNameAndVersion());
- System.out.println("mainClass:" + md.mainClass().get());
+ md.mainClass().ifPresent(mc -> System.out.println("mainClass:" + mc));
+
+ StringJoiner sj = new StringJoiner(",");
+ md.requires().stream().map(ModuleDescriptor.Requires::name).sorted().forEach(sj::add);
+ System.out.println("requires:" + sj.toString());
+
+ sj = new StringJoiner(",");
+ md.exports().stream().map(ModuleDescriptor.Exports::source).sorted().forEach(sj::add);
+ if (!sj.toString().equals(""))
+ System.out.println("exports:" + sj.toString());
+
+ sj = new StringJoiner(",");
+ md.uses().stream().sorted().forEach(sj::add);
+ if (!sj.toString().equals(""))
+ System.out.println("uses:" + sj.toString());
+
+ sj = new StringJoiner(",");
+ md.provides().keySet().stream().sorted().forEach(sj::add);
+ if (!sj.toString().equals(""))
+ System.out.println("provides:" + sj.toString());
+
+ sj = new StringJoiner(",");
+ md.conceals().forEach(sj::add);
+ if (!sj.toString().equals(""))
+ System.out.println("conceals:" + sj.toString());
Method m = ModuleDescriptor.class.getDeclaredMethod("hashes");
m.setAccessible(true);
ModuleDescriptor foo = jdk.test.foo.Foo.class.getModule().getDescriptor();
- Optional<ModuleHashes> oHashes =
- (Optional<ModuleHashes>) m.invoke(foo);
-
+ Optional<ModuleHashes> oHashes = (Optional<ModuleHashes>) m.invoke(foo);
System.out.println("hashes:" + oHashes.get().hashFor("bar"));
-
- StringJoiner sj = new StringJoiner(",");
- md.conceals().forEach(sj::add);
- System.out.println("conceals:" + sj.toString());
}
}
--- a/jdk/test/tools/jar/modularJar/src/bar/jdk/test/bar/internal/Message.java Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/bar/jdk/test/bar/internal/Message.java Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
+ * 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
--- a/jdk/test/tools/jar/modularJar/src/bar/module-info.java Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/bar/module-info.java Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
+ * 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
--- a/jdk/test/tools/jar/modularJar/src/baz/jdk/test/baz/BazService.java Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/baz/jdk/test/baz/BazService.java Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
+ * 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
--- a/jdk/test/tools/jar/modularJar/src/baz/jdk/test/baz/internal/BazServiceImpl.java Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/baz/jdk/test/baz/internal/BazServiceImpl.java Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
+ * 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
--- a/jdk/test/tools/jar/modularJar/src/baz/module-info.java Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/baz/module-info.java Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
+ * 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
--- a/jdk/test/tools/jar/modularJar/src/foo/jdk/test/foo/Foo.java Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/foo/jdk/test/foo/Foo.java Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
+ * 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
@@ -26,6 +24,8 @@
package jdk.test.foo;
import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleDescriptor.Exports;
+import java.lang.module.ModuleDescriptor.Requires;
import java.util.StringJoiner;
import jdk.test.foo.internal.Message;
@@ -36,10 +36,30 @@
ModuleDescriptor md = Foo.class.getModule().getDescriptor();
System.out.println("nameAndVersion:" + md.toNameAndVersion());
- System.out.println("mainClass:" + md.mainClass().get());
+ md.mainClass().ifPresent(mc -> System.out.println("mainClass:" + mc));
StringJoiner sj = new StringJoiner(",");
+ md.requires().stream().map(Requires::name).sorted().forEach(sj::add);
+ System.out.println("requires:" + sj.toString());
+
+ sj = new StringJoiner(",");
+ md.exports().stream().map(Exports::source).sorted().forEach(sj::add);
+ if (!sj.toString().equals(""))
+ System.out.println("exports:" + sj.toString());
+
+ sj = new StringJoiner(",");
+ md.uses().stream().sorted().forEach(sj::add);
+ if (!sj.toString().equals(""))
+ System.out.println("uses:" + sj.toString());
+
+ sj = new StringJoiner(",");
+ md.provides().keySet().stream().sorted().forEach(sj::add);
+ if (!sj.toString().equals(""))
+ System.out.println("provides:" + sj.toString());
+
+ sj = new StringJoiner(",");
md.conceals().forEach(sj::add);
- System.out.println("conceals:" + sj.toString());
+ if (!sj.toString().equals(""))
+ System.out.println("conceals:" + sj.toString());
}
}
--- a/jdk/test/tools/jar/modularJar/src/foo/jdk/test/foo/internal/Message.java Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/foo/jdk/test/foo/internal/Message.java Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
+ * 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
--- a/jdk/test/tools/jar/modularJar/src/foo/module-info.java Mon May 23 12:44:55 2016 -0700
+++ b/jdk/test/tools/jar/modularJar/src/foo/module-info.java Mon May 23 21:28:39 2016 +0100
@@ -1,12 +1,10 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
+ * 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