diff --git a/core/src/main/scala/com/eed3si9n/jarjarabrams/Zip.scala b/core/src/main/scala/com/eed3si9n/jarjarabrams/Zip.scala index 1bc3df5..0e1cf7a 100644 --- a/core/src/main/scala/com/eed3si9n/jarjarabrams/Zip.scala +++ b/core/src/main/scala/com/eed3si9n/jarjarabrams/Zip.scala @@ -1,7 +1,7 @@ package com.eed3si9n.jarjarabrams -import com.eed3si9n.jarjar.util.{ DuplicateJarEntryException, EntryStruct } -import java.nio.file.{ Files, NoSuchFileException, Path, StandardCopyOption } +import com.eed3si9n.jarjar.util.{ DuplicateJarEntryException, EntryStruct, IoUtil } +import java.nio.file.{ Files, NoSuchFileException, Path } import java.nio.file.attribute.FileTime import java.io.{ ByteArrayOutputStream, FileNotFoundException, InputStream, OutputStream } import java.security.MessageDigest @@ -83,7 +83,7 @@ object Zip { } } } - Files.move(tempJar, outputJar, StandardCopyOption.REPLACE_EXISTING) + IoUtil.copyZipWithoutEmptyDirectories(tempJar.toFile, outputJar.toFile) resetModifiedTime(outputJar) outputJar } diff --git a/core/src/test/scala/testpkg/ShaderTest.scala b/core/src/test/scala/testpkg/ShaderTest.scala index 9247516..3dc345d 100644 --- a/core/src/test/scala/testpkg/ShaderTest.scala +++ b/core/src/test/scala/testpkg/ShaderTest.scala @@ -15,7 +15,7 @@ object ShaderTest extends BasicTestSuite { Paths.get(byteBuddyJar), resetTimestamp = false, expectedClass = expectedByteBuddyClass, - expectedSha = "673a5b1d7282ec68def6d6e6845c29d96142e4e3b39796484e122cd92f65edee" + expectedSha = "42454701a0b53a13af17d015c1785ef5ea342d8c324315ed17d80831cba98be3" ) } @@ -24,7 +24,7 @@ object ShaderTest extends BasicTestSuite { Paths.get(byteBuddyJar), resetTimestamp = true, expectedClass = expectedByteBuddyClass, - expectedSha = "33ceee11fb2b5e4d46ebe552025bc17bc4d9391974c55e07d63f9e85d2ec381a" + expectedSha = "0db0b1300533c06a934dca1e7016f6dc2d432c66f1927102d6f6b49086dcfddb" ) } diff --git a/jarjar/src/main/java/com/eed3si9n/jarjar/util/IoUtil.java b/jarjar/src/main/java/com/eed3si9n/jarjar/util/IoUtil.java index 29d758d..95cb28e 100644 --- a/jarjar/src/main/java/com/eed3si9n/jarjar/util/IoUtil.java +++ b/jarjar/src/main/java/com/eed3si9n/jarjar/util/IoUtil.java @@ -17,10 +17,7 @@ package com.eed3si9n.jarjar.util; import java.io.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Enumeration; +import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; @@ -53,82 +50,53 @@ public static void copy(File from, File to, byte[] buf) throws IOException { } /** - * Create a copy of an zip file without its empty directories. + * Create a copy of a zip file, removing all empty directories. * @param inputFile * @param outputFile * @throws IOException */ - public static void copyZipWithoutEmptyDirectories(final File inputFile, final File outputFile) throws IOException - { + public static void copyZipWithoutEmptyDirectories(File inputFile, File outputFile) throws IOException { final byte[] buf = new byte[0x2000]; + ZipFile inputZip = new ZipFile(inputFile); + ZipOutputStream outputStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outputFile))); - final ZipFile inputZip = new ZipFile(inputFile); - BufferedOutputStream buffered = new BufferedOutputStream(new FileOutputStream(outputFile)); - - final ZipOutputStream outputStream = new ZipOutputStream(buffered); - try - { - // read a the entries of the input zip file and sort them - final Enumeration e = inputZip.entries(); - final ArrayList sortedList = new ArrayList(); - while (e.hasMoreElements()) { - final ZipEntry entry = e.nextElement(); - sortedList.add(entry); - } - - Collections.sort(sortedList, new Comparator() - { - public int compare(ZipEntry o1, ZipEntry o2) - { - return o1.getName().compareTo(o2.getName()); - } - }); - - // treat them again and write them in output, wenn they not are empty directories - for (int i = sortedList.size()-1; i>=0; i--) - { - final ZipEntry inputEntry = sortedList.get(i); - final String name = inputEntry.getName(); - final boolean isEmptyDirectory; - if (inputEntry.isDirectory()) - { - if (i == sortedList.size()-1) - { - // no item afterwards; it was an empty directory - isEmptyDirectory = true; - } - else - { - final String nextName = sortedList.get(i+1).getName(); - isEmptyDirectory = !nextName.startsWith(name); - } - } - else - { - isEmptyDirectory = false; + // First pass: create a set of directories to retain + Set dirsToRetain = new HashSet<>(); + Enumeration entries = inputZip.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (!entry.isDirectory()) { + String name = entry.getName(); + int index = name.lastIndexOf('/'); + while (index > 0) { + name = name.substring(0, index); + dirsToRetain.add(name + "/"); + index = name.lastIndexOf('/'); } + } + } - - // write the entry - if (isEmptyDirectory) - { - sortedList.remove(inputEntry); - } - else - { - final ZipEntry outputEntry = new ZipEntry(inputEntry); - outputStream.putNextEntry(outputEntry); + // Second pass: copy entries, excluding directories not in the set + entries = inputZip.entries(); + while (entries.hasMoreElements()) { + ZipEntry inputEntry = entries.nextElement(); + if (!inputEntry.isDirectory() || dirsToRetain.contains(inputEntry.getName())) { + ZipEntry outputEntry = new ZipEntry(inputEntry); + outputEntry.setCompressedSize(-1); + outputStream.putNextEntry(outputEntry); + if (!inputEntry.isDirectory()) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); final InputStream is = inputZip.getInputStream(inputEntry); IoUtil.pipe(is, baos, buf); is.close(); outputStream.write(baos.toByteArray()); } + outputStream.closeEntry(); } - } finally { - outputStream.close(); } + outputStream.close(); + inputZip.close(); } } diff --git a/jarjar/src/test/java/com/eed3si9n/jarjar/util/IOUtilTest.java b/jarjar/src/test/java/com/eed3si9n/jarjar/util/IOUtilTest.java new file mode 100644 index 0000000..9c4a2e3 --- /dev/null +++ b/jarjar/src/test/java/com/eed3si9n/jarjar/util/IOUtilTest.java @@ -0,0 +1,68 @@ +package com.eed3si9n.jarjar.util; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public class IOUtilTest { + @Test + public void testCopyZipWithoutEmptyDirectories() throws IOException { + // create a temporary directory tree with a few empty files and empty directories + Path tree = Files.createTempDirectory("tree"); + Path zips = Files.createTempDirectory("zips"); + + // Create a zip with some empty directories + Path a = Files.createDirectory(Paths.get(tree.toString(), "a")); + Files.createFile(Paths.get(a.toString(), "a.txt")); + Files.createDirectory(Paths.get(tree.toString(), "b")); + Path c = Files.createDirectory(Paths.get(tree.toString(), "c")); + Files.createDirectory(Paths.get(c.toString(), "d")); + File inputZipFile = Paths.get(zips.toString(), "input.zip").toFile(); + zipDirectory(tree, inputZipFile); + + File outputZipFile = Paths.get(zips.toString(), "output.zip").toFile(); + IoUtil.copyZipWithoutEmptyDirectories(inputZipFile, outputZipFile); + try (ZipFile outputZip = new ZipFile(outputZipFile)) { + Assert.assertNotNull(outputZip.getEntry("a/a.txt")); + Assert.assertNotNull(outputZip.getEntry("a/")); + Assert.assertNull(outputZip.getEntry("b/")); + Assert.assertNull(outputZip.getEntry("c/")); + Assert.assertNull(outputZip.getEntry("c/d/")); + } + } + + private static void zipDirectory(Path sourceDir, File zipFile) throws IOException { + try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile)); + Stream paths = Files.walk(sourceDir) + ) { + paths.forEach(path -> { + String name = sourceDir.relativize(path).toString(); + if (Files.isDirectory(path)) { + name = name + "/"; + } + ZipEntry zipEntry = new ZipEntry(name); + try { + zipOutputStream.putNextEntry(zipEntry); + if (!Files.isDirectory(path)) { + Files.copy(path, zipOutputStream); + } + zipOutputStream.closeEntry(); + } catch (IOException e) { + throw new RuntimeIOException(e); + } + }); + } + } + + +}