8165944: jar utility doesn't process more than one -C argument
Reviewed-by: psandoz
--- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java Mon Oct 03 15:39:02 2016 +0200
+++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java Mon Oct 03 10:57:29 2016 -0700
@@ -103,6 +103,18 @@
basename = en.baseName;
entryname = en.entryName;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Entry)) return false;
+ return this.file.equals(((Entry)o).file);
+ }
+
+ @Override
+ public int hashCode() {
+ return file.hashCode();
+ }
}
class EntryName {
@@ -124,10 +136,10 @@
if (name.startsWith("./")) {
name = name.substring(2);
}
- this.baseName = name;
- this.entryName = (version > BASE_VERSION)
- ? VERSIONS_DIR + version + "/" + this.baseName
- : this.baseName;
+ baseName = name;
+ entryName = (version > BASE_VERSION)
+ ? VERSIONS_DIR + version + "/" + baseName
+ : baseName;
}
}
@@ -137,7 +149,7 @@
Map<String, Entry> entryMap = new HashMap<>();
// All entries need to be added/updated.
- Map<String, Entry> entries = new LinkedHashMap<>();
+ Set<Entry> entries = new LinkedHashSet<>();
// All packages.
Set<String> packages = new HashSet<>();
@@ -855,8 +867,7 @@
moduleInfoPaths.put(entryName, f.toPath());
if (isUpdate)
entryMap.put(entryName, entry);
- } else if (!entries.containsKey(entryName)) {
- entries.put(entryName, entry);
+ } else if (entries.add(entry)) {
jarEntries.add(entryName);
if (entry.basename.endsWith(".class") && !entryName.startsWith(VERSIONS_DIR))
packages.add(toPackageName(entry.basename));
@@ -864,8 +875,7 @@
entryMap.put(entryName, entry);
}
} else if (f.isDirectory()) {
- if (!entries.containsKey(entryName)) {
- entries.put(entryName, entry);
+ if (entries.add(entry)) {
if (isUpdate) {
entryMap.put(entryName, entry);
}
@@ -923,8 +933,7 @@
in.transferTo(zos);
zos.closeEntry();
}
- for (String entryname : entries.keySet()) {
- Entry entry = entries.get(entryname);
+ for (Entry entry : entries) {
addFile(zos, entry);
}
zos.close();
@@ -1049,7 +1058,7 @@
Entry ent = entryMap.get(name);
addFile(zos, ent);
entryMap.remove(name);
- entries.remove(name);
+ entries.remove(ent);
}
jarEntries.add(name);
@@ -1059,8 +1068,8 @@
}
// add the remaining new files
- for (String entryname : entries.keySet()) {
- addFile(zos, entries.get(entryname));
+ for (Entry entry : entries) {
+ addFile(zos, entry);
}
if (!foundManifest) {
if (newManifest != null) {
@@ -1248,6 +1257,9 @@
* Adds a new file entry to the ZIP output stream.
*/
void addFile(ZipOutputStream zos, Entry entry) throws IOException {
+ // skip the generation of directory entries for META-INF/versions/*/
+ if (entry.basename.isEmpty()) return;
+
File file = entry.file;
String name = entry.entryname;
boolean isDir = entry.isDir;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jar/InputFilesTest.java Mon Oct 03 10:57:29 2016 -0700
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8165944
+ * @summary test several jar tool input file scenarios with variations on -C
+ * options with/without a --release option. Some input files are
+ * duplicates that sometimes cause exceptions and other times do not,
+ * demonstrating identical behavior to JDK 8 jar tool.
+ * @library /lib/testlibrary
+ * @modules jdk.jartool/sun.tools.jar
+ * @build jdk.testlibrary.FileUtils
+ * @run testng InputFilesTest
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.stream.Stream;
+import java.util.zip.ZipException;
+
+import jdk.testlibrary.FileUtils;
+
+public class InputFilesTest {
+ private final String nl = System.lineSeparator();
+ private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ private final PrintStream out = new PrintStream(baos);
+ private Runnable onCompletion;
+
+ @BeforeMethod
+ public void reset() {
+ onCompletion = null;
+ }
+
+ @AfterMethod
+ public void run() {
+ if (onCompletion != null) {
+ onCompletion.run();
+ }
+ }
+
+ @Test
+ public void test1() throws IOException {
+ mkdir("test1 test2");
+ touch("test1/testfile1 test2/testfile2");
+ jar("cf test.jar -C test1 . -C test2 .");
+ jar("tf test.jar");
+ println();
+ String output = "META-INF/" + nl +
+ "META-INF/MANIFEST.MF" + nl +
+ "testfile1" + nl +
+ "testfile2" + nl;
+ rm("test.jar test1 test2");
+ Assert.assertEquals(baos.toByteArray(), output.getBytes());
+ }
+
+ @Test
+ public void test2() throws IOException {
+ mkdir("test1 test2 test3 test4");
+ touch("test1/testfile1 test2/testfile2 test3/testfile3 test4/testfile4");
+ jar("cf test.jar -C test1 . -C test2 . --release 9 -C test3 . -C test4 .");
+ jar("tf test.jar");
+ println();
+ String output = "META-INF/" + nl +
+ "META-INF/MANIFEST.MF" + nl +
+ "testfile1" + nl +
+ "testfile2" + nl +
+ "META-INF/versions/9/testfile3" + nl +
+ "META-INF/versions/9/testfile4" + nl;
+ rm("test.jar test1 test2 test3 test4");
+ Assert.assertEquals(baos.toByteArray(), output.getBytes());
+ }
+
+ @Test
+ public void test3() throws IOException {
+ touch("test");
+ jar("cf test.jar test test");
+ jar("tf test.jar");
+ println();
+ String output = "META-INF/" + nl +
+ "META-INF/MANIFEST.MF" + nl +
+ "test" + nl;
+ rm("test.jar test");
+ Assert.assertEquals(baos.toByteArray(), output.getBytes());
+ }
+
+ @Test
+ public void test4() throws IOException {
+ mkdir("a");
+ touch("a/test");
+ jar("cf test.jar -C a test -C a test");
+ jar("tf test.jar");
+ println();
+ String output = "META-INF/" + nl +
+ "META-INF/MANIFEST.MF" + nl +
+ "test" + nl;
+ rm("test.jar a");
+ Assert.assertEquals(baos.toByteArray(), output.getBytes());
+ }
+
+ @Test(expectedExceptions = {ZipException.class})
+ public void test5() throws IOException {
+ mkdir("a");
+ touch("test a/test");
+ onCompletion = () -> rm("test a");
+ jar("cf test.jar -C a test test");
+ }
+
+ @Test(expectedExceptions = {ZipException.class})
+ public void test6() throws IOException {
+ mkdir("test1 test2");
+ touch("test1/a test2/a");
+ onCompletion = () -> rm("test1 test2");
+ jar("cf test.jar --release 9 -C test1 a -C test2 a");
+ }
+
+ private Stream<Path> mkpath(String... args) {
+ return Arrays.stream(args).map(d -> Paths.get(".", d.split("/")));
+ }
+
+ private void mkdir(String cmdline) {
+ System.out.println("mkdir -p " + cmdline);
+ mkpath(cmdline.split(" +")).forEach(p -> {
+ try {
+ Files.createDirectories(p);
+ } catch (IOException x) {
+ throw new UncheckedIOException(x);
+ }
+ });
+ }
+
+ private void touch(String cmdline) {
+ System.out.println("touch " + cmdline);
+ mkpath(cmdline.split(" +")).forEach(p -> {
+ try {
+ Files.createFile(p);
+ } catch (IOException x) {
+ throw new UncheckedIOException(x);
+ }
+ });
+ }
+
+ private void rm(String cmdline) {
+ System.out.println("rm -rf " + cmdline);
+ mkpath(cmdline.split(" +")).forEach(p -> {
+ try {
+ if (Files.isDirectory(p)) {
+ FileUtils.deleteFileTreeWithRetry(p);
+ } else {
+ FileUtils.deleteFileIfExistsWithRetry(p);
+ }
+ } catch (IOException x) {
+ throw new UncheckedIOException(x);
+ }
+ });
+ }
+
+ private void jar(String cmdline) throws IOException {
+ System.out.println("jar " + cmdline);
+ baos.reset();
+
+ // the run method catches IOExceptions, we need to expose them
+ ByteArrayOutputStream baes = new ByteArrayOutputStream();
+ PrintStream err = new PrintStream(baes);
+ PrintStream saveErr = System.err;
+ System.setErr(err);
+ boolean ok = new sun.tools.jar.Main(out, err, "jar").run(cmdline.split(" +"));
+ System.setErr(saveErr);
+ if (!ok) {
+ String s = baes.toString();
+ if (s.startsWith("java.util.zip.ZipException: duplicate entry: ")) {
+ throw new ZipException(s);
+ }
+ throw new IOException(s);
+ }
+ }
+
+ private void println() throws IOException {
+ System.out.println(new String(baos.toByteArray()));
+ }
+}
--- a/jdk/test/tools/jar/multiRelease/Basic.java Mon Oct 03 15:39:02 2016 +0200
+++ b/jdk/test/tools/jar/multiRelease/Basic.java Mon Oct 03 10:57:29 2016 -0700
@@ -195,6 +195,8 @@
new String[] {"v10", "version", "Version.class"}
);
+ compare(jarfile, names);
+
delete(jarfile);
deleteDir(Paths.get(usr, "classes"));
}