# HG changeset patch # User chegar # Date 1464014924 -3600 # Mon May 23 15:48:44 2016 +0100 # Node ID 2c3aa3569fcce4c80f06d939a2014a0c466eb31e # Parent 4352ddb02f68bff4b2162d6c35fb487f412704aa 8156497: Add jar tool support for Multi-Release Modular JARs Reviewed-by: alanb diff --git a/src/jdk.jartool/share/classes/sun/tools/jar/Main.java b/src/jdk.jartool/share/classes/sun/tools/jar/Main.java --- a/src/jdk.jartool/share/classes/sun/tools/jar/Main.java +++ b/src/jdk.jartool/share/classes/sun/tools/jar/Main.java @@ -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 moduleInfoPaths = new HashMap<>(); + expand(null, files, false, moduleInfoPaths); - byte[] moduleInfoBytes = null; - if (isModularJar()) { - moduleInfoBytes = addExtendedModuleAttributes( - readModuleInfo(moduleInfo)); + Map moduleInfos = new LinkedHashMap<>(); + if (!moduleInfoPaths.isEmpty()) { + if (!checkModuleInfos(moduleInfoPaths)) + return false; + + // root module-info first + byte[] b = readModuleInfo(moduleInfoPaths.get(MODULE_INFO)); + moduleInfos.put(MODULE_INFO, b); + for (Map.Entry 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); - byte[] moduleInfoBytes = null; - if (isModularJar()) { - moduleInfoBytes = readModuleInfo(moduleInfo); - } + Map moduleInfoPaths = new HashMap<>(); + expand(null, files, true, moduleInfoPaths); + + Map moduleInfos = new HashMap<>(); + for (Map.Entry 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 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 moduleInfos) throws IOException { ZipOutputStream zos = new JarOutputStream(out); @@ -710,17 +724,19 @@ manifest.write(zos); zos.closeEntry(); } - if (moduleInfoBytes != null) { + for (Map.Entry 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 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 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 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 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 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 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 exports = rd.exports() + .stream() + .map(Exports::source) + .collect(toSet()); + + Set conceals = packages.stream() + .filter(p -> !exports.contains(p)) + .collect(toSet()); + + for (Map.Entry 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 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 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 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); + // Add (or replace) the ConcealedPackages attribute + extender.conceals(conceals); + + // --main-class + if (ename != null) + extender.mainClass(ename); + else if (rootDescriptor.mainClass().isPresent()) + extender.mainClass(rootDescriptor.mainClass().get()); + + // --module-version + if (moduleVersion != null) + extender.version(moduleVersion); + else if (rootDescriptor.version().isPresent()) + extender.version(rootDescriptor.version().get()); + + // --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); + } } - String name = md.name(); - Set exported = md.exports() - .stream() - .map(ModuleDescriptor.Exports::source) - .collect(Collectors.toSet()); - // 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 conceals = packages.stream() - .filter(p -> !exported.contains(p)) - .collect(Collectors.toSet()); - - extender.conceals(conceals); - - // --main-class - if (ename != null) - extender.mainClass(ename); - - // --module-version - if (moduleVersion != null) - extender.version(moduleVersion); - - // --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); - } - } - - extender.write(baos); - return baos.toByteArray(); - } + extender.write(baos); + return baos.toByteArray(); } /** @@ -1865,8 +1983,8 @@ deque.add(name); Set 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 modulesForHash = mods.stream() .filter(mn -> !mn.equals(name) && modules.contains(mn)) .collect(Collectors.toMap(Function.identity(), moduleNameToPath::get)); diff --git a/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties b/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties --- a/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties +++ b/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties @@ -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=\ diff --git a/test/tools/jar/modularJar/Basic.java b/test/tools/jar/modularJar/Basic.java --- a/test/tools/jar/modularJar/Basic.java +++ b/test/tools/jar/modularJar/Basic.java @@ -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 requires; + final Set exports; + final Set uses; + final Set provides; final String mainClass; final String version; final String message; final String hashes; final Set 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 requires, Set exports, Set uses, + Set provides, Set 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 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 conceals = null; + Set 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 stringToSet(String commaList) { + Set 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 s1, Set 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) diff --git a/test/tools/jar/modularJar/src/bar/jdk/test/bar/Bar.java b/test/tools/jar/modularJar/src/bar/jdk/test/bar/Bar.java --- a/test/tools/jar/modularJar/src/bar/jdk/test/bar/Bar.java +++ b/test/tools/jar/modularJar/src/bar/jdk/test/bar/Bar.java @@ -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 oHashes = - (Optional) m.invoke(foo); - + Optional oHashes = (Optional) 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()); } } diff --git a/test/tools/jar/modularJar/src/bar/jdk/test/bar/internal/Message.java b/test/tools/jar/modularJar/src/bar/jdk/test/bar/internal/Message.java --- a/test/tools/jar/modularJar/src/bar/jdk/test/bar/internal/Message.java +++ b/test/tools/jar/modularJar/src/bar/jdk/test/bar/internal/Message.java @@ -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 diff --git a/test/tools/jar/modularJar/src/bar/module-info.java b/test/tools/jar/modularJar/src/bar/module-info.java --- a/test/tools/jar/modularJar/src/bar/module-info.java +++ b/test/tools/jar/modularJar/src/bar/module-info.java @@ -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 diff --git a/test/tools/jar/modularJar/src/baz/jdk/test/baz/BazService.java b/test/tools/jar/modularJar/src/baz/jdk/test/baz/BazService.java --- a/test/tools/jar/modularJar/src/baz/jdk/test/baz/BazService.java +++ b/test/tools/jar/modularJar/src/baz/jdk/test/baz/BazService.java @@ -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 diff --git a/test/tools/jar/modularJar/src/baz/jdk/test/baz/internal/BazServiceImpl.java b/test/tools/jar/modularJar/src/baz/jdk/test/baz/internal/BazServiceImpl.java --- a/test/tools/jar/modularJar/src/baz/jdk/test/baz/internal/BazServiceImpl.java +++ b/test/tools/jar/modularJar/src/baz/jdk/test/baz/internal/BazServiceImpl.java @@ -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 diff --git a/test/tools/jar/modularJar/src/baz/module-info.java b/test/tools/jar/modularJar/src/baz/module-info.java --- a/test/tools/jar/modularJar/src/baz/module-info.java +++ b/test/tools/jar/modularJar/src/baz/module-info.java @@ -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 diff --git a/test/tools/jar/modularJar/src/foo/jdk/test/foo/Foo.java b/test/tools/jar/modularJar/src/foo/jdk/test/foo/Foo.java --- a/test/tools/jar/modularJar/src/foo/jdk/test/foo/Foo.java +++ b/test/tools/jar/modularJar/src/foo/jdk/test/foo/Foo.java @@ -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()); } } diff --git a/test/tools/jar/modularJar/src/foo/jdk/test/foo/internal/Message.java b/test/tools/jar/modularJar/src/foo/jdk/test/foo/internal/Message.java --- a/test/tools/jar/modularJar/src/foo/jdk/test/foo/internal/Message.java +++ b/test/tools/jar/modularJar/src/foo/jdk/test/foo/internal/Message.java @@ -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 diff --git a/test/tools/jar/modularJar/src/foo/module-info.java b/test/tools/jar/modularJar/src/foo/module-info.java --- a/test/tools/jar/modularJar/src/foo/module-info.java +++ b/test/tools/jar/modularJar/src/foo/module-info.java @@ -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