# HG changeset patch # User sdrach # Date 1475517449 25200 # Node ID eb0dc5ea230210a6e4007f1c1d13441107691268 # Parent ebb2fec04e82df0d1123bd90398d6a40c0c62927 8165944: jar utility doesn't process more than one -C argument Reviewed-by: psandoz diff -r ebb2fec04e82 -r eb0dc5ea2302 jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.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 entryMap = new HashMap<>(); // All entries need to be added/updated. - Map entries = new LinkedHashMap<>(); + Set entries = new LinkedHashSet<>(); // All packages. Set 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; diff -r ebb2fec04e82 -r eb0dc5ea2302 jdk/test/tools/jar/InputFilesTest.java --- /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 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())); + } +} diff -r ebb2fec04e82 -r eb0dc5ea2302 jdk/test/tools/jar/multiRelease/Basic.java --- 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")); }