8165944: jar utility doesn't process more than one -C argument
authorsdrach
Mon, 03 Oct 2016 10:57:29 -0700
changeset 41221 eb0dc5ea2302
parent 41220 ebb2fec04e82
child 41222 7215e27667e9
child 41410 ef26c8e40f1e
8165944: jar utility doesn't process more than one -C argument Reviewed-by: psandoz
jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java
jdk/test/tools/jar/InputFilesTest.java
jdk/test/tools/jar/multiRelease/Basic.java
--- 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"));
     }