--- old/src/jdk.jartool/share/classes/sun/tools/jar/FingerPrint.java 2017-11-29 08:37:47.239697316 -0800 +++ new/src/jdk.jartool/share/classes/sun/tools/jar/FingerPrint.java 2017-11-29 08:37:46.837661044 -0800 @@ -43,7 +43,7 @@ * a class, it also contains information to (1) describe the public API; * (2) compare the public API of this class with another class; (3) determine * whether or not it's a nested class and, if so, the name of the associated - * top level class; and (4) for an canonically ordered set of classes determine + * outer class; and (4) for an canonically ordered set of classes determine * if the class versions are compatible. A set of classes is canonically * ordered if the classes in the set have the same name, and the base class * precedes the versioned classes and if each versioned class with version @@ -53,10 +53,13 @@ final class FingerPrint { private static final MessageDigest MD; + private final String basename; + private final String entryName; + private final int mrversion; + private final byte[] sha1; private final ClassAttributes attrs; private final boolean isClassEntry; - private final String entryName; static { try { @@ -67,16 +70,19 @@ } } - public FingerPrint(String entryName,byte[] bytes) throws IOException { + public FingerPrint(String basename, String entryName, int mrversion, byte[] bytes) + throws IOException { + this.basename = basename; this.entryName = entryName; - if (entryName.endsWith(".class") && isCafeBabe(bytes)) { + this.mrversion = mrversion; + if (isCafeBabe(bytes)) { isClassEntry = true; sha1 = sha1(bytes, 8); // skip magic number and major/minor version attrs = getClassAttributes(bytes); } else { isClassEntry = false; - sha1 = sha1(bytes); - attrs = new ClassAttributes(); // empty class + sha1 = null; + attrs = null; } } @@ -107,14 +113,24 @@ return attrs.equals(that.attrs); } - public String name() { - String name = attrs.name; - return name == null ? entryName : name; + public String basename() { + return basename; + } + + public String entryName() { + return entryName; + } + + public String className() { + return attrs.name; + } + + public int mrversion() { + return mrversion; } - public String topLevelName() { - String name = attrs.topLevelName; - return name == null ? name() : name; + public String outerClassName() { + return attrs.outerClassName; } private byte[] sha1(byte[] entry) { @@ -218,7 +234,7 @@ private static final class ClassAttributes extends ClassVisitor { private String name; - private String topLevelName; + private String outerClassName; private String superName; private int version; private int access; @@ -228,7 +244,7 @@ private final Set methods = new HashSet<>(); public ClassAttributes() { - super(Opcodes.ASM5); + super(Opcodes.ASM6); } private boolean isPublic(int access) { @@ -250,7 +266,7 @@ @Override public void visitOuterClass(String owner, String name, String desc) { if (!this.nestedClass) return; - this.topLevelName = owner; + this.outerClassName = owner; } @Override @@ -259,7 +275,7 @@ if (!this.nestedClass) return; if (outerName == null) return; if (!this.name.equals(name)) return; - if (this.topLevelName == null) this.topLevelName = outerName; + if (this.outerClassName == null) this.outerClassName = outerName; } @Override @@ -294,7 +310,7 @@ @Override public void visitEnd() { - this.nestedClass = this.topLevelName != null; + this.nestedClass = this.outerClassName != null; } @Override --- old/src/jdk.jartool/share/classes/sun/tools/jar/Main.java 2017-11-29 08:37:48.290792146 -0800 +++ new/src/jdk.jartool/share/classes/sun/tools/jar/Main.java 2017-11-29 08:37:47.891756145 -0800 @@ -71,7 +71,6 @@ import static java.util.jar.JarFile.MANIFEST_NAME; import static java.util.stream.Collectors.joining; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; -import static sun.tools.jar.Validator.ENTRYNAME_COMPARATOR; /** * This class implements a simple utility for creating files in the JAR @@ -444,8 +443,8 @@ private void validateAndClose(File tmpfile) throws IOException { if (ok && isMultiRelease) { - try (JarFile jf = new JarFile(tmpfile)) { - ok = Validator.validate(this, jf); + try (ZipFile zf = new ZipFile(tmpfile)) { + ok = Validator.validate(this, zf); if (!ok) { error(formatMsg("error.validator.jarfile.invalid", fname)); } @@ -1784,7 +1783,7 @@ private boolean describeModule(ZipFile zipFile) throws IOException { ZipFileModuleInfoEntry[] infos = zipFile.stream() .filter(e -> isModuleInfoEntry(e.getName())) - .sorted(Validator.ENTRY_COMPARATOR) + .sorted(ENTRY_COMPARATOR) .map(e -> new ZipFileModuleInfoEntry(zipFile, e)) .toArray(ZipFileModuleInfoEntry[]::new); @@ -2216,4 +2215,48 @@ return hashesBuilder.computeHashes(Set.of(name)).get(name); } } + + // sort base entries before versioned entries, and sort entry classes with + // nested classes so that the outter class appears before the associated + // nested class + static Comparator ENTRYNAME_COMPARATOR = (s1, s2) -> { + + if (s1.equals(s2)) return 0; + boolean b1 = s1.startsWith(VERSIONS_DIR); + boolean b2 = s2.startsWith(VERSIONS_DIR); + if (b1 && !b2) return 1; + if (!b1 && b2) return -1; + int n = 0; // starting char for String compare + if (b1 && b2) { + // normally strings would be sorted so "10" goes before "9", but + // version number strings need to be sorted numerically + n = VERSIONS_DIR.length(); // skip the common prefix + int i1 = s1.indexOf('/', n); + int i2 = s2.indexOf('/', n); + if (i1 == -1) throw new Validator.InvalidJarException(s1); + if (i2 == -1) throw new Validator.InvalidJarException(s2); + // shorter version numbers go first + if (i1 != i2) return i1 - i2; + // otherwise, handle equal length numbers below + } + int l1 = s1.length(); + int l2 = s2.length(); + int lim = Math.min(l1, l2); + for (int k = n; k < lim; k++) { + char c1 = s1.charAt(k); + char c2 = s2.charAt(k); + if (c1 != c2) { + // change natural ordering so '.' comes before '$' + // i.e. outer classes come before nested classes + if (c1 == '$' && c2 == '.') return 1; + if (c1 == '.' && c2 == '$') return -1; + return c1 - c2; + } + } + return l1 - l2; + }; + + static Comparator ENTRY_COMPARATOR = + Comparator.comparing(ZipEntry::getName, ENTRYNAME_COMPARATOR); + } --- old/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java 2017-11-29 08:37:49.422894285 -0800 +++ new/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java 2017-11-29 08:37:48.885845832 -0800 @@ -28,24 +28,22 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.lang.module.InvalidModuleDescriptorException; import java.lang.module.ModuleDescriptor; import java.lang.module.ModuleDescriptor.Exports; -import java.lang.module.InvalidModuleDescriptorException; import java.lang.module.ModuleDescriptor.Opens; import java.lang.module.ModuleDescriptor.Provides; import java.lang.module.ModuleDescriptor.Requires; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Consumer; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import static java.util.jar.JarFile.MANIFEST_NAME; import static sun.tools.jar.Main.VERSIONS_DIR; @@ -55,269 +53,184 @@ import static sun.tools.jar.Main.formatMsg; import static sun.tools.jar.Main.formatMsg2; import static sun.tools.jar.Main.toBinaryName; -import static sun.tools.jar.Main.isModuleInfoEntry; final class Validator { - private final static boolean DEBUG = Boolean.getBoolean("jar.debug"); - private final Map fps = new HashMap<>(); + + private final Map classes = new HashMap<>(); private final Main main; - private final JarFile jf; - private int oldVersion = -1; - private String currentTopLevelName; + private final ZipFile zf; private boolean isValid = true; private Set concealedPkgs = Collections.emptySet(); private ModuleDescriptor md; private String mdName; - private Validator(Main main, JarFile jf) { + private Validator(Main main, ZipFile zf) { this.main = main; - this.jf = jf; + this.zf = zf; checkModuleDescriptor(MODULE_INFO); } - static boolean validate(Main main, JarFile jf) throws IOException { - return new Validator(main, jf).validate(); + static boolean validate(Main main, ZipFile zf) throws IOException { + return new Validator(main, zf).validate(); } private boolean validate() { try { - jf.stream() - .filter(e -> !e.isDirectory() && - !e.getName().equals(MANIFEST_NAME)) - .sorted(ENTRY_COMPARATOR) - .forEachOrdered(e -> validate(e)); - return isValid; + zf.stream() + .filter(e -> e.getName().endsWith(".class")) + .map(this::getFingerPrint) + .filter(FingerPrint::isClass) // skip any non-class entry + .collect(Collectors.groupingBy( + FingerPrint::mrversion, + TreeMap::new, + Collectors.toMap(FingerPrint::className, + Function.identity(), + this::sameNameFingerPrint))) + .forEach((version, entries) -> { + if (version == 0) + validateBase(entries); + else + validateVersioned(entries); + }); } catch (InvalidJarException e) { - error(formatMsg("error.validator.bad.entry.name", e.getMessage())); + errorAndInvalid(e.getMessage()); } - return false; + return isValid; } - private static class InvalidJarException extends RuntimeException { + static class InvalidJarException extends RuntimeException { private static final long serialVersionUID = -3642329147299217726L; InvalidJarException(String msg) { super(msg); } } - // 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 - static Comparator ENTRYNAME_COMPARATOR = (s1, s2) -> { - - if (s1.equals(s2)) return 0; - boolean b1 = s1.startsWith(VERSIONS_DIR); - boolean b2 = s2.startsWith(VERSIONS_DIR); - if (b1 && !b2) return 1; - if (!b1 && b2) return -1; - int n = 0; // starting char for String compare - if (b1 && b2) { - // normally strings would be sorted so "10" goes before "9", but - // version number strings need to be sorted numerically - n = VERSIONS_DIR.length(); // skip the common prefix - int i1 = s1.indexOf('/', n); - int i2 = s2.indexOf('/', n); - if (i1 == -1) throw new InvalidJarException(s1); - if (i2 == -1) throw new InvalidJarException(s2); - // shorter version numbers go first - if (i1 != i2) return i1 - i2; - // otherwise, handle equal length numbers below - } - int l1 = s1.length(); - int l2 = s2.length(); - int lim = Math.min(l1, l2); - for (int k = n; k < lim; k++) { - char c1 = s1.charAt(k); - char c2 = s2.charAt(k); - if (c1 != c2) { - // change natural ordering so '.' comes before '$' - // i.e. top level classes come before nested classes - if (c1 == '$' && c2 == '.') return 1; - if (c1 == '.' && c2 == '$') return -1; - return c1 - c2; - } - } - return l1 - l2; - }; - - static Comparator 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 - * ascending version order. Also, to find isolated nested classes, - * classes must be ordered so that the top level class is before the associated - * nested class(es). - */ - public void validate(JarEntry je) { - String entryName = je.getName(); - - // directories are always accepted - if (entryName.endsWith("/")) { - debug("%s is a directory", entryName); - return; - } + private FingerPrint sameNameFingerPrint(FingerPrint fp1, FingerPrint fp2) { + checkClassName(fp1); + checkClassName(fp2); + // entries/classes with same name, return fp2 for now ? + return fp2; + } - // validate the versioned module-info - if (isModuleInfoEntry(entryName)) { - if (!entryName.equals(mdName)) - checkModuleDescriptor(entryName); - return; - } + private FingerPrint getFingerPrint(ZipEntry ze) { + // figure out the version and basename from the ZipEntry + String ename = ze.getName(); + String bname = ename; + int version = 0; - // 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("/", VERSIONS_DIR_LENGTH); + if (ename.startsWith(VERSIONS_DIR)) { + int n = ename.indexOf("/", VERSIONS_DIR_LENGTH); if (n == -1) { - error(formatMsg("error.validator.version.notnumber", entryName)); - isValid = false; - return; + throw new InvalidJarException( + formatMsg("error.validator.version.notnumber", ename)); } - versionStr = entryName.substring(VERSIONS_DIR_LENGTH, n); try { - version = Integer.parseInt(versionStr); + version = Integer.parseInt(ename, VERSIONS_DIR_LENGTH, n, 10); } catch (NumberFormatException x) { - error(formatMsg("error.validator.version.notnumber", entryName)); - isValid = false; - return; + throw new InvalidJarException( + formatMsg("error.validator.version.notnumber", ename)); } - if (n == entryName.length()) { - error(formatMsg("error.validator.entryname.tooshort", entryName)); - isValid = false; - return; + if (n == ename.length()) { + throw new InvalidJarException( + formatMsg("error.validator.entryname.tooshort", ename)); } - basename = entryName.substring(n + 1); - } else { - version = 0; - basename = entryName; + bname = ename.substring(n + 1); } - debug("\n===================\nversion %d %s", version, entryName); - 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 - FingerPrint fp; - try (InputStream is = jf.getInputStream(je)) { - fp = new FingerPrint(basename, is.readAllBytes()); + // return the cooresponding fingerprint entry + try (InputStream is = zf.getInputStream(ze)) { + return new FingerPrint(bname, ename, version, is.readAllBytes()); } catch (IOException x) { - error(x.getMessage()); - isValid = false; - return; + throw new InvalidJarException(x.getMessage()); } - String internalName = fp.name(); + } - // process a base entry paying attention to nested classes - if (version == 0) { - debug("base entry found"); - if (fp.isNestedClass()) { - debug("nested class found"); - if (fp.topLevelName().equals(currentTopLevelName)) { - fps.put(internalName, fp); - return; - } - error(formatMsg("error.validator.isolated.nested.class", entryName)); + /* + * Validates (a) if there is any isolated nested class, and (b) if the + * class name in class file (by asm) matches the entry's basename. + */ + public void validateBase(Map fps) { + fps.values().forEach( fp -> { + if (!checkClassName(fp)) { isValid = false; - return; - } - // top level class or resource entry - if (fp.isClass()) { - currentTopLevelName = fp.topLevelName(); - if (!checkInternalName(entryName, basename, internalName)) { + return; + } + if (fp.isNestedClass()) { + if (!checkNestedClass(fp, fps)) { isValid = false; - return; } - } - fps.put(internalName, fp); - return; - } + } + classes.put(fp.className(), fp); + }); + } + + public void validateVersioned(Map fps) { + + fps.values().forEach( fp -> { - // process a versioned entry, look for previous entry with same name - FingerPrint matchFp = fps.get(internalName); - debug("looking for match"); - if (matchFp == null) { - debug("no match found"); - if (fp.isClass()) { + // validate the versioned module-info + if (MODULE_INFO.equals(fp.basename())) { + checkModuleDescriptor(fp.entryName()); + return; + } + // process a versioned entry, look for previous entry with same name + FingerPrint matchFp = classes.get(fp.className()); + if (matchFp == null) { + // no match found if (fp.isNestedClass()) { - if (!checkNestedClass(version, entryName, internalName, fp)) { + if (!checkNestedClass(fp, fps)) { isValid = false; } return; } if (fp.isPublicClass()) { - if (!isConcealed(internalName)) { - error(Main.formatMsg("error.validator.new.public.class", entryName)); - isValid = false; + if (!isConcealed(fp.className())) { + errorAndInvalid(formatMsg("error.validator.new.public.class", + fp.entryName())); return; - } - warn(formatMsg("warn.validator.concealed.public.class", entryName)); - debug("%s is a public class entry in a concealed package", entryName); + } + // entry is a public class entry in a concealed package + warn(formatMsg("warn.validator.concealed.public.class", + fp.entryName())); } - debug("%s is a non-public class entry", entryName); - fps.put(internalName, fp); - currentTopLevelName = fp.topLevelName(); + classes.put(fp.className(), fp); return; } - debug("%s is a resource entry"); - fps.put(internalName, fp); - return; - } - debug("match found"); - // are the two classes/resources identical? - if (fp.isIdentical(matchFp)) { - warn(formatMsg("warn.validator.identical.entry", entryName)); - return; // it's okay, just takes up room - } - debug("sha1 not equal -- different bytes"); + // are the two classes/resources identical? + if (fp.isIdentical(matchFp)) { + warn(formatMsg("warn.validator.identical.entry", fp.entryName())); + return; // it's okay, just takes up room + } - // ok, not identical, check for compatible class version and api - if (fp.isClass()) { + // ok, not identical, check for compatible class version and api if (fp.isNestedClass()) { - if (!checkNestedClass(version, entryName, internalName, fp)) { + if (!checkNestedClass(fp, fps)) { isValid = false; } - return; + return; // fall through, need check nested public class?? } - debug("%s is a class entry", entryName); if (!fp.isCompatibleVersion(matchFp)) { - error(formatMsg("error.validator.incompatible.class.version", entryName)); - isValid = false; + errorAndInvalid(formatMsg("error.validator.incompatible.class.version", + fp.entryName())); return; } if (!fp.isSameAPI(matchFp)) { - error(formatMsg("error.validator.different.api", entryName)); - isValid = false; + errorAndInvalid(formatMsg("error.validator.different.api", + fp.entryName())); return; } - if (!checkInternalName(entryName, basename, internalName)) { + if (!checkClassName(fp)) { isValid = false; return; } - debug("fingerprints same -- same api"); - fps.put(internalName, fp); - currentTopLevelName = fp.topLevelName(); - return; - } - debug("%s is a resource", entryName); + classes.put(fp.className(), fp); - warn(formatMsg("warn.validator.resources.with.same.name", entryName)); - fps.put(internalName, fp); - return; + return; + }); } - /** + /* * Checks whether or not the given versioned module descriptor's attributes * are valid when compared against the root/base module descriptor. * @@ -326,12 +239,12 @@ * - 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. + * service types defined outside of the platform modules. */ private void checkModuleDescriptor(String miName) { - ZipEntry je = jf.getEntry(miName); - if (je != null) { - try (InputStream jis = jf.getInputStream(je)) { + ZipEntry ze = zf.getEntry(miName); + if (ze != null) { + try (InputStream jis = zf.getInputStream(ze)) { 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 @@ -344,7 +257,7 @@ // 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) + .filter(p -> zf.getEntry(toBinaryName(p)) == null) .peek(p -> error(formatMsg("error.missing.provider", p))) .count() != 0) { isValid = false; @@ -356,8 +269,7 @@ } if (!base.name().equals(md.name())) { - error(getMsg("error.validator.info.name.notequal")); - isValid = false; + errorAndInvalid(getMsg("error.validator.info.name.notequal")); } if (!base.requires().equals(md.requires())) { Set baseRequires = base.requires(); @@ -365,11 +277,9 @@ if (baseRequires.contains(r)) continue; if (r.modifiers().contains(Requires.Modifier.TRANSITIVE)) { - error(getMsg("error.validator.info.requires.transitive")); - isValid = false; + errorAndInvalid(getMsg("error.validator.info.requires.transitive")); } else if (!isPlatformModule(r.name())) { - error(getMsg("error.validator.info.requires.added")); - isValid = false; + errorAndInvalid(getMsg("error.validator.info.requires.added")); } } for (Requires r : baseRequires) { @@ -377,87 +287,78 @@ if (mdRequires.contains(r)) continue; if (!isPlatformModule(r.name())) { - error(getMsg("error.validator.info.requires.dropped")); - isValid = false; + errorAndInvalid(getMsg("error.validator.info.requires.dropped")); } } } if (!base.exports().equals(md.exports())) { - error(getMsg("error.validator.info.exports.notequal")); - isValid = false; + errorAndInvalid(getMsg("error.validator.info.exports.notequal")); } if (!base.opens().equals(md.opens())) { - error(getMsg("error.validator.info.opens.notequal")); - isValid = false; + errorAndInvalid(getMsg("error.validator.info.opens.notequal")); } if (!base.provides().equals(md.provides())) { - error(getMsg("error.validator.info.provides.notequal")); - isValid = false; + errorAndInvalid(getMsg("error.validator.info.provides.notequal")); } if (!base.mainClass().equals(md.mainClass())) { - error(formatMsg("error.validator.info.manclass.notequal", je.getName())); - isValid = false; + errorAndInvalid(formatMsg("error.validator.info.manclass.notequal", + ze.getName())); } if (!base.version().equals(md.version())) { - error(formatMsg("error.validator.info.version.notequal", je.getName())); - isValid = false; + errorAndInvalid(formatMsg("error.validator.info.version.notequal", + ze.getName())); } } catch (Exception x) { - error(x.getMessage() + " : " + miName); - this.isValid = false; + errorAndInvalid(x.getMessage() + " : " + miName); } } } - 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)) { + private boolean checkClassName(FingerPrint fp) { + if (fp.className().equals(className(fp.basename()))) { return true; } error(formatMsg2("error.validator.names.mismatch", - entryName, internalName.replace("/", "."))); + fp.entryName(), fp.className().replace("/", "."))); return false; } - private boolean checkNestedClass(int version, String entryName, String internalName, FingerPrint fp) { - debug("%s is a nested class entry in top level class %s", entryName, fp.topLevelName()); - if (fp.topLevelName().equals(currentTopLevelName)) { - debug("%s (top level class) was accepted", fp.topLevelName()); - fps.put(internalName, fp); + private boolean checkNestedClass(FingerPrint fp, Map outerClasses) { + if (outerClasses.containsKey(fp.outerClassName())) { return true; } - debug("top level class was not accepted"); - error(formatMsg("error.validator.isolated.nested.class", entryName)); + // outer class was not available + error(formatMsg("error.validator.isolated.nested.class", fp.entryName())); return false; } - private String className(String entryName) { - return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null; - } - - private boolean isConcealed(String internalName) { + private boolean isConcealed(String className) { if (concealedPkgs.isEmpty()) { return false; } - int idx = internalName.lastIndexOf('/'); - String pkgName = idx != -1 ? internalName.substring(0, idx).replace('/', '.') : ""; + int idx = className.lastIndexOf('/'); + String pkgName = idx != -1 ? className.substring(0, idx).replace('/', '.') : ""; return concealedPkgs.contains(pkgName); } - private void debug(String fmt, Object... args) { - if (DEBUG) System.err.format(fmt, args); + private static boolean isPlatformModule(String name) { + return name.startsWith("java.") || name.startsWith("jdk."); + } + + private static String className(String entryName) { + return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null; } private void error(String msg) { main.error(msg); } + private void errorAndInvalid(String msg) { + main.error(msg); + isValid = false; + } + private void warn(String msg) { main.warn(msg); } - } --- old/test/jdk/tools/jar/multiRelease/Basic.java 2017-11-29 08:37:50.256969535 -0800 +++ new/test/jdk/tools/jar/multiRelease/Basic.java 2017-11-29 08:37:49.909938226 -0800 @@ -23,6 +23,7 @@ /* * @test + # @bug 8186087 * @library /test/lib * @modules java.base/jdk.internal.misc * jdk.compiler @@ -346,39 +347,6 @@ } @Test - // resources with same name in different versions - // this is okay but produces warning - public void test08() throws Throwable { - String jarfile = "test.jar"; - - compile("test01"); //use same data as test01 - - Path classes = Paths.get("classes"); - - // add a resource to the base - Path source = Paths.get(src, "data", "test01", "base", "version"); - Files.copy(source.resolve("Version.java"), classes.resolve("base") - .resolve("version").resolve("Version.java")); - - jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", - "--release", "9", "-C", classes.resolve("v9").toString(), ".") - .shouldHaveExitValue(SUCCESS) - .shouldBeEmpty(); - - // now add a different resource with same name to META-INF/version/9 - Files.copy(source.resolve("Main.java"), classes.resolve("v9") - .resolve("version").resolve("Version.java")); - - jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", - "--release", "9", "-C", classes.resolve("v9").toString(), ".") - .shouldHaveExitValue(SUCCESS) - .shouldContain("multiple resources with same name"); - - FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); - FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); - } - - @Test // a class with an internal name different from the external name public void test09() throws Throwable { String jarfile = "test.jar"; @@ -451,7 +419,9 @@ .shouldNotHaveExitValue(SUCCESS) .asLines(); + /* "META-INF/versions/9/version/Nested$nested.class" is really NOT isolated assertTrue(output.size() == 4); + assertTrue(output.size() == 3); assertTrue(output.get(0).contains("an isolated nested class"), output.get(0)); assertTrue(output.get(1).contains("contains a new public class"), @@ -460,6 +430,17 @@ output.get(2)); assertTrue(output.get(3).contains("invalid multi-release jar file"), output.get(3)); + assertTrue(output.get(2).contains("invalid multi-release jar file"), + output.get(2)); + */ + + assertTrue(output.size() == 3); + assertTrue(output.get(0).contains("an isolated nested class"), + output.get(0)); + assertTrue(output.get(1).contains("contains a new public class"), + output.get(1)); + assertTrue(output.get(2).contains("invalid multi-release jar file"), + output.get(2)); FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); @@ -495,6 +476,31 @@ } @Test + // assure the nested-nested classes are acceptable + public void test13() throws Throwable { + String jarfile = "test.jar"; + + compile("test01"); //use same data as test01 + + Path classes = Paths.get("classes"); + + // add a base class with a nested and nested-nested class + Path source = Paths.get(src, "data", "test13", "base", "version"); + javac(classes.resolve("base"), source.resolve("Nested.java")); + + // add a versioned class with a nested and nested-nested class + source = Paths.get(src, "data", "test13", "v10", "version"); + javac(classes.resolve("v10"), source.resolve("Nested.java")); + + jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", + "--release", "10", "-C", classes.resolve("v10").toString(), ".") + .shouldHaveExitValue(SUCCESS); + + FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); + FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); + } + + @Test public void testCustomManifest() throws Throwable { String jarfile = "test.jar"; --- old/test/jdk/tools/jar/multiRelease/whitebox/jdk.jartool/sun/tools/jar/ValidatorComparatorTest.java 2017-11-29 08:37:51.216056064 -0800 +++ new/test/jdk/tools/jar/multiRelease/whitebox/jdk.jartool/sun/tools/jar/ValidatorComparatorTest.java 2017-11-29 08:37:50.705009957 -0800 @@ -29,7 +29,7 @@ import java.util.List; import static java.util.stream.Collectors.toList; -import static sun.tools.jar.Validator.ENTRYNAME_COMPARATOR; +import static sun.tools.jar.Main.ENTRYNAME_COMPARATOR; import org.testng.Assert; import org.testng.annotations.Test; --- /dev/null 2017-07-29 19:06:40.011000000 -0700 +++ new/test/jdk/tools/jar/multiRelease/data/test13/base/version/Nested.java 2017-11-29 08:37:51.778106772 -0800 @@ -0,0 +1,22 @@ +package version; + +public class Nested { + public int getVersion() { + return 9; + } + + protected void doNothing() { + } + + private void anyName() { + } + + class nested { + int save; + + class nestnested { + int save; + } + + } +} --- /dev/null 2017-07-29 19:06:40.011000000 -0700 +++ new/test/jdk/tools/jar/multiRelease/data/test13/v10/version/Nested.java 2017-11-29 08:37:52.769196188 -0800 @@ -0,0 +1,18 @@ +package version; + +public class Nested { + public int getVersion() { + return 10; + } + + protected void doNothing() { + } + + class nested { + int save = getVersion(); + + class nestnested { + int save = getVersion();; + } + } +}