8165640: Enhance jar tool to allow module-info in versioned directories but not in base in modular multi-release jar files
Reviewed-by: psandoz, mchung
--- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java Fri Feb 10 21:58:45 2017 +0000
+++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java Sat Feb 11 21:31:43 2017 -0800
@@ -158,7 +158,7 @@
static final String MANIFEST_DIR = "META-INF/";
static final String VERSIONS_DIR = MANIFEST_DIR + "versions/";
static final String VERSION = "1.0";
-
+ static final int VERSIONS_DIR_LENGTH = VERSIONS_DIR.length();
private static ResourceBundle rsrc;
/**
@@ -681,7 +681,7 @@
void addPackageIfNamed(Set<String> packages, String name) {
if (name.startsWith(VERSIONS_DIR)) {
// trim the version dir prefix
- int i0 = VERSIONS_DIR.length();
+ int i0 = VERSIONS_DIR_LENGTH;
int i = name.indexOf('/', i0);
if (i <= 0) {
warn(formatMsg("warn.release.unexpected.versioned.entry", name));
@@ -1727,12 +1727,16 @@
private boolean printModuleDescriptor(ZipFile zipFile)
throws IOException
{
- ZipEntry entry = zipFile.getEntry(MODULE_INFO);
- if (entry == null)
+ ZipEntry[] zes = zipFile.stream()
+ .filter(e -> isModuleInfoEntry(e.getName()))
+ .sorted(Validator.ENTRY_COMPARATOR)
+ .toArray(ZipEntry[]::new);
+ if (zes.length == 0)
return false;
-
- try (InputStream is = zipFile.getInputStream(entry)) {
- printModuleDescriptor(is);
+ for (ZipEntry ze : zes) {
+ try (InputStream is = zipFile.getInputStream(ze)) {
+ printModuleDescriptor(is, ze.getName());
+ }
}
return true;
}
@@ -1742,16 +1746,23 @@
{
try (BufferedInputStream bis = new BufferedInputStream(fis);
ZipInputStream zis = new ZipInputStream(bis)) {
-
ZipEntry e;
while ((e = zis.getNextEntry()) != null) {
- if (e.getName().equals(MODULE_INFO)) {
- printModuleDescriptor(zis);
- return true;
+ String ename = e.getName();
+ if (isModuleInfoEntry(ename)){
+ moduleInfos.put(ename, zis.readAllBytes());
}
}
}
- return false;
+ if (moduleInfos.size() == 0)
+ return false;
+ String[] names = moduleInfos.keySet().stream()
+ .sorted(Validator.ENTRYNAME_COMPARATOR)
+ .toArray(String[]::new);
+ for (String name : names) {
+ printModuleDescriptor(new ByteArrayInputStream(moduleInfos.get(name)), name);
+ }
+ return true;
}
static <T> String toString(Collection<T> set) {
@@ -1760,7 +1771,7 @@
.collect(joining(" "));
}
- private void printModuleDescriptor(InputStream entryInputStream)
+ private void printModuleDescriptor(InputStream entryInputStream, String ename)
throws IOException
{
ModuleInfo.Attributes attrs = ModuleInfo.read(entryInputStream, null);
@@ -1768,10 +1779,12 @@
ModuleHashes hashes = attrs.recordedHashes();
StringBuilder sb = new StringBuilder();
- sb.append("\n");
+ sb.append("\nmodule ")
+ .append(md.toNameAndVersion())
+ .append(" (").append(ename).append(")");
+
if (md.isOpen())
- sb.append("open ");
- sb.append(md.toNameAndVersion());
+ sb.append("\n open ");
md.requires().stream()
.sorted(Comparator.comparing(Requires::name))
@@ -1879,7 +1892,7 @@
if (end == 0)
return true;
if (name.startsWith(VERSIONS_DIR)) {
- int off = VERSIONS_DIR.length();
+ int off = VERSIONS_DIR_LENGTH;
if (off == end) // meta-inf/versions/module-info.class
return false;
while (off < end - 1) {
--- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java Fri Feb 10 21:58:45 2017 +0000
+++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java Sat Feb 11 21:31:43 2017 -0800
@@ -49,6 +49,7 @@
import static java.util.jar.JarFile.MANIFEST_NAME;
import static sun.tools.jar.Main.VERSIONS_DIR;
+import static sun.tools.jar.Main.VERSIONS_DIR_LENGTH;
import static sun.tools.jar.Main.MODULE_INFO;
import static sun.tools.jar.Main.getMsg;
import static sun.tools.jar.Main.formatMsg;
@@ -59,19 +60,19 @@
final class Validator {
private final static boolean DEBUG = Boolean.getBoolean("jar.debug");
private final Map<String,FingerPrint> fps = new HashMap<>();
- private static final int vdlen = VERSIONS_DIR.length();
private final Main main;
private final JarFile jf;
private int oldVersion = -1;
private String currentTopLevelName;
private boolean isValid = true;
- private Set<String> concealedPkgs;
+ private Set<String> concealedPkgs = Collections.emptySet();
private ModuleDescriptor md;
+ private String mdName;
private Validator(Main main, JarFile jf) {
this.main = main;
this.jf = jf;
- loadModuleDescriptor();
+ checkModuleDescriptor(MODULE_INFO);
}
static boolean validate(Main main, JarFile jf) throws IOException {
@@ -83,7 +84,7 @@
jf.stream()
.filter(e -> !e.isDirectory() &&
!e.getName().equals(MANIFEST_NAME))
- .sorted(entryComparator)
+ .sorted(ENTRY_COMPARATOR)
.forEachOrdered(e -> validate(e));
return isValid;
} catch (InvalidJarException e) {
@@ -102,9 +103,8 @@
// sort base entries before versioned entries, and sort entry classes with
// nested classes so that the top level class appears before the associated
// nested class
- private static Comparator<JarEntry> entryComparator = (je1, je2) -> {
- String s1 = je1.getName();
- String s2 = je2.getName();
+ static Comparator<String> ENTRYNAME_COMPARATOR = (s1, s2) -> {
+
if (s1.equals(s2)) return 0;
boolean b1 = s1.startsWith(VERSIONS_DIR);
boolean b2 = s2.startsWith(VERSIONS_DIR);
@@ -140,6 +140,9 @@
return l1 - l2;
};
+ static Comparator<ZipEntry> ENTRY_COMPARATOR =
+ Comparator.comparing(ZipEntry::getName, ENTRYNAME_COMPARATOR);
+
/*
* Validator has state and assumes entries provided to accept are ordered
* from base entries first and then through the versioned entries in
@@ -158,24 +161,25 @@
// validate the versioned module-info
if (isModuleInfoEntry(entryName)) {
- if (entryName.length() != MODULE_INFO.length())
- checkModuleDescriptor(je);
+ if (!entryName.equals(mdName))
+ checkModuleDescriptor(entryName);
return;
}
// figure out the version and basename from the JarEntry
int version;
String basename;
+ String versionStr = null;;
if (entryName.startsWith(VERSIONS_DIR)) {
- int n = entryName.indexOf("/", vdlen);
+ int n = entryName.indexOf("/", VERSIONS_DIR_LENGTH);
if (n == -1) {
error(formatMsg("error.validator.version.notnumber", entryName));
isValid = false;
return;
}
- String v = entryName.substring(vdlen, n);
+ versionStr = entryName.substring(VERSIONS_DIR_LENGTH, n);
try {
- version = Integer.parseInt(v);
+ version = Integer.parseInt(versionStr);
} catch (NumberFormatException x) {
error(formatMsg("error.validator.version.notnumber", entryName));
isValid = false;
@@ -196,6 +200,11 @@
if (oldVersion != version) {
oldVersion = version;
currentTopLevelName = null;
+ if (md == null && versionStr != null) {
+ // don't have a base module-info.class yet, try to see if
+ // a versioned one exists
+ checkModuleDescriptor(VERSIONS_DIR + versionStr + "/" + MODULE_INFO);
+ }
}
// analyze the entry, keeping key attributes
@@ -308,61 +317,52 @@
return;
}
- private void loadModuleDescriptor() {
- ZipEntry je = jf.getEntry(MODULE_INFO);
- if (je != null) {
- try (InputStream jis = jf.getInputStream(je)) {
- md = ModuleDescriptor.read(jis);
- concealedPkgs = new HashSet<>(md.packages());
- md.exports().stream().map(Exports::source).forEach(concealedPkgs::remove);
- md.opens().stream().map(Opens::source).forEach(concealedPkgs::remove);
- return;
- } catch (Exception x) {
- error(x.getMessage() + " : " + je.getName());
- this.isValid = false;
- }
- }
- md = null;
- concealedPkgs = Collections.emptySet();
- }
-
- private static boolean isPlatformModule(String name) {
- return name.startsWith("java.") || name.startsWith("jdk.");
- }
-
/**
* Checks whether or not the given versioned module descriptor's attributes
- * are valid when compared against the root module descriptor.
+ * are valid when compared against the root/base module descriptor.
*
- * A versioned module descriptor must be identical to the root module
+ * A versioned module descriptor must be identical to the root/base 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 void checkModuleDescriptor(JarEntry je) {
- try (InputStream is = jf.getInputStream(je)) {
- ModuleDescriptor root = this.md;
- ModuleDescriptor md = null;
- try {
- md = ModuleDescriptor.read(is);
- } catch (InvalidModuleDescriptorException x) {
- error(x.getMessage());
- isValid = false;
- return;
- }
- if (root == null) {
- this.md = md;
- } else {
- if (!root.name().equals(md.name())) {
+ private void checkModuleDescriptor(String miName) {
+ ZipEntry je = jf.getEntry(miName);
+ if (je != null) {
+ try (InputStream jis = jf.getInputStream(je)) {
+ ModuleDescriptor md = ModuleDescriptor.read(jis);
+ // Initialize the base md if it's not yet. A "base" md can be either the
+ // root module-info.class or the first versioned module-info.class
+ ModuleDescriptor base = this.md;
+
+ if (base == null) {
+ concealedPkgs = new HashSet<>(md.packages());
+ md.exports().stream().map(Exports::source).forEach(concealedPkgs::remove);
+ md.opens().stream().map(Opens::source).forEach(concealedPkgs::remove);
+ // must have the implementation class of the services it 'provides'.
+ if (md.provides().stream().map(Provides::providers)
+ .flatMap(List::stream)
+ .filter(p -> jf.getEntry(toBinaryName(p)) == null)
+ .peek(p -> error(formatMsg("error.missing.provider", p)))
+ .count() != 0) {
+ isValid = false;
+ return;
+ }
+ this.md = md;
+ this.mdName = miName;
+ return;
+ }
+
+ if (!base.name().equals(md.name())) {
error(getMsg("error.validator.info.name.notequal"));
isValid = false;
}
- if (!root.requires().equals(md.requires())) {
- Set<Requires> rootRequires = root.requires();
+ if (!base.requires().equals(md.requires())) {
+ Set<Requires> baseRequires = base.requires();
for (Requires r : md.requires()) {
- if (rootRequires.contains(r))
+ if (baseRequires.contains(r))
continue;
if (r.modifiers().contains(Requires.Modifier.TRANSITIVE)) {
error(getMsg("error.validator.info.requires.transitive"));
@@ -372,7 +372,7 @@
isValid = false;
}
}
- for (Requires r : rootRequires) {
+ for (Requires r : baseRequires) {
Set<Requires> mdRequires = md.requires();
if (mdRequires.contains(r))
continue;
@@ -382,33 +382,37 @@
}
}
}
- if (!root.exports().equals(md.exports())) {
+ if (!base.exports().equals(md.exports())) {
error(getMsg("error.validator.info.exports.notequal"));
isValid = false;
}
- if (!root.opens().equals(md.opens())) {
+ if (!base.opens().equals(md.opens())) {
error(getMsg("error.validator.info.opens.notequal"));
isValid = false;
}
- if (!root.provides().equals(md.provides())) {
+ if (!base.provides().equals(md.provides())) {
error(getMsg("error.validator.info.provides.notequal"));
isValid = false;
}
- if (!root.mainClass().equals(md.mainClass())) {
+ if (!base.mainClass().equals(md.mainClass())) {
error(formatMsg("error.validator.info.manclass.notequal", je.getName()));
isValid = false;
}
- if (!root.version().equals(md.version())) {
+ if (!base.version().equals(md.version())) {
error(formatMsg("error.validator.info.version.notequal", je.getName()));
isValid = false;
}
+ } catch (Exception x) {
+ error(x.getMessage() + " : " + miName);
+ this.isValid = false;
}
- } catch (IOException x) {
- error(x.getMessage());
- isValid = false;
}
}
+ private static boolean isPlatformModule(String name) {
+ return name.startsWith("java.") || name.startsWith("jdk.");
+ }
+
private boolean checkInternalName(String entryName, String basename, String internalName) {
String className = className(basename);
if (internalName.equals(className)) {
--- a/jdk/test/tools/jar/mmrjar/Basic.java Fri Feb 10 21:58:45 2017 +0000
+++ b/jdk/test/tools/jar/mmrjar/Basic.java Sat Feb 11 21:31:43 2017 -0800
@@ -268,7 +268,7 @@
actual = lines(outbytes);
expected = Set.of(
- "hi",
+ "module hi (module-info.class)",
"requires mandated java.base",
"contains p",
"contains p.internal"
@@ -304,7 +304,7 @@
actual = lines(outbytes);
expected = Set.of(
- "hi",
+ "module hi (module-info.class)",
"requires mandated java.base",
"contains p",
"contains p.internal",
@@ -396,18 +396,18 @@
Paths.get("test7-v10", "module-info.class"));
int rc = jar("--create --file mmr.jar --main-class=p.Main -C test7 . --release 9 -C test7-v9 . --release 10 -C test7-v10 .");
-
-System.out.println("-----------------------");
-System.out.println( new String(errbytes.toByteArray()));
-
-
Assert.assertEquals(rc, 0);
-
- jar("-tf mmr.jar");
-
-System.out.println("-----------------------");
-System.out.println( new String(outbytes.toByteArray()));
+ jar("-d --file=mmr.jar");
+ System.out.println("-----------------------");
+ System.out.println( new String(outbytes.toByteArray()));
+ Assert.assertEquals(lines(outbytes),
+ Set.of(
+ "module m1 (META-INF/versions/9/module-info.class)",
+ "module m1 (META-INF/versions/10/module-info.class)",
+ "requires mandated java.base",
+ "exports p",
+ "main-class p.Main"));
Optional<String> exp = Optional.of("p.Main");
try (ZipFile zf = new ZipFile("mmr.jar")) {
--- a/jdk/test/tools/jar/modularJar/Basic.java Fri Feb 10 21:58:45 2017 +0000
+++ b/jdk/test/tools/jar/modularJar/Basic.java Sat Feb 11 21:31:43 2017 -0800
@@ -46,7 +46,7 @@
/*
* @test
- * @bug 8167328 8171830
+ * @bug 8167328 8171830 8165640
* @library /lib/testlibrary
* @modules jdk.compiler
* jdk.jartool
@@ -241,11 +241,6 @@
java(mp, FOO.moduleName + "/" + FOO.mainClass)
.assertSuccess()
-.resultChecker(r -> {
- System.out.println("===================================");
- System.out.println(r.output);
- System.out.println("===================================");
-})
.resultChecker(r -> assertModuleData(r, FOO));
try (InputStream fis = Files.newInputStream(modularJar);
JarInputStream jis = new JarInputStream(fis)) {
@@ -484,7 +479,7 @@
.resultChecker(r -> {
// Expect similar output: "bar, requires mandated foo, ...
// conceals jdk.test.foo, conceals jdk.test.foo.internal"
- Pattern p = Pattern.compile("\\s+bar\\s+requires\\s++foo");
+ Pattern p = Pattern.compile("module bar \\(module-info.class\\)\\s+requires\\s++foo");
assertTrue(p.matcher(r.output).find(),
"Expecting to find \"bar, requires foo,...\"",
"in output, but did not: [" + r.output + "]");
@@ -740,6 +735,53 @@
}
@Test
+ public void servicesCreateWithoutFailureNonRootMRMJAR() throws IOException {
+ // without a root module-info.class
+ Path mp = Paths.get("servicesCreateWithoutFailureNonRootMRMJAR");
+ 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(),
+ "--main-class=" + "jdk.test.baz.Baz",
+ "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
+ "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
+ "-C", modClasses.toString(), "jdk/test/baz/BazService.class",
+ "-C", modClasses.toString(), "jdk/test/baz/Baz.class",
+ "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class")
+ .assertSuccess();
+
+
+ for (String option : new String[] {"--print-module-descriptor", "-d" }) {
+
+ jar(option,
+ "--file=" + modularJar.toString())
+ .assertSuccess()
+ .resultChecker(r ->
+ assertTrue(r.output.contains("main-class jdk.test.baz.Baz"),
+ "Expected to find ", "main-class jdk.test.baz.Baz",
+ " in [", r.output, "]"));
+
+ jarWithStdin(modularJar.toFile(), option)
+ .assertSuccess()
+ .resultChecker(r ->
+ assertTrue(r.output.contains("main-class jdk.test.baz.Baz"),
+ "Expected to find ", "main-class jdk.test.baz.Baz",
+ " in [", r.output, "]"));
+
+ }
+ // run module maain class
+ java(mp, "baz/jdk.test.baz.Baz")
+ .assertSuccess()
+ .resultChecker(r ->
+ assertTrue(r.output.contains("mainClass:jdk.test.baz.Baz"),
+ "Expected to find ", "mainClass:jdk.test.baz.Baz",
+ " in [", r.output, "]"));
+ }
+
+ @Test
public void exportCreateWithMissingPkg() throws IOException {
Path foobar = TEST_SRC.resolve("src").resolve("foobar");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jar/modularJar/src/baz/jdk/test/baz/Baz.java Sat Feb 11 21:31:43 2017 -0800
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017, 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.
+ */
+
+package jdk.test.baz;
+
+import java.lang.module.ModuleDescriptor;
+
+public class Baz {
+ public static void main(String[] args) {
+ ModuleDescriptor md = Baz.class.getModule().getDescriptor();
+ System.out.println("nameAndVersion:" + md.toNameAndVersion());
+ md.mainClass().ifPresent(mc -> System.out.println("mainClass:" + mc));
+ }
+}