Skip to content

Commit

Permalink
Remove empty dirs (#54)
Browse files Browse the repository at this point in the history
* Reimplemented copyZipWithoutEmptyDirectories to remove nested empty directories

* - Now removes empty directories by calling copyZipWithoutEmptyDirectories in Zip.scala, mirroring the behavior of StandaloneJarProcessor.java
- Reimplemented copyZipWithoutEmptyDirectories to remove nested empty directories, and added a test for it.
- Update SHA of shading tests of bytebuddy. A before and after diff of unzip -l shows no change in any file paths, timestamps and sizes, except for the two empty directories that get removed as expected:

7a8,9
>         0  01-03-1980 00:00   net/
>         0  01-03-1980 00:00   net/bytebuddy/
93c95
<    733298                     88 files
---
>    733298                     90 files

* - Adding setCompressedSize(-1) in copyZipWithoutEmptyDirectories() makes SHAs for shading tests consistent across JDK versions 8, 11 and 21.

* - Added back missing BufferedOutputStream in copyZipWithoutEmptyDirectories
  • Loading branch information
patrick-premont authored Feb 28, 2024
1 parent 38ed500 commit 519cc62
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 68 deletions.
6 changes: 3 additions & 3 deletions core/src/main/scala/com/eed3si9n/jarjarabrams/Zip.scala
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -83,7 +83,7 @@ object Zip {
}
}
}
Files.move(tempJar, outputJar, StandardCopyOption.REPLACE_EXISTING)
IoUtil.copyZipWithoutEmptyDirectories(tempJar.toFile, outputJar.toFile)
resetModifiedTime(outputJar)
outputJar
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/test/scala/testpkg/ShaderTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ object ShaderTest extends BasicTestSuite {
Paths.get(byteBuddyJar),
resetTimestamp = false,
expectedClass = expectedByteBuddyClass,
expectedSha = "673a5b1d7282ec68def6d6e6845c29d96142e4e3b39796484e122cd92f65edee"
expectedSha = "42454701a0b53a13af17d015c1785ef5ea342d8c324315ed17d80831cba98be3"
)
}

Expand All @@ -24,7 +24,7 @@ object ShaderTest extends BasicTestSuite {
Paths.get(byteBuddyJar),
resetTimestamp = true,
expectedClass = expectedByteBuddyClass,
expectedSha = "33ceee11fb2b5e4d46ebe552025bc17bc4d9391974c55e07d63f9e85d2ec381a"
expectedSha = "0db0b1300533c06a934dca1e7016f6dc2d432c66f1927102d6f6b49086dcfddb"
)
}

Expand Down
94 changes: 31 additions & 63 deletions jarjar/src/main/java/com/eed3si9n/jarjar/util/IoUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<? extends ZipEntry> e = inputZip.entries();
final ArrayList<ZipEntry> sortedList = new ArrayList<ZipEntry>();
while (e.hasMoreElements()) {
final ZipEntry entry = e.nextElement();
sortedList.add(entry);
}

Collections.sort(sortedList, new Comparator<ZipEntry>()
{
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<String> dirsToRetain = new HashSet<>();
Enumeration<? extends ZipEntry> 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();
}

}
68 changes: 68 additions & 0 deletions jarjar/src/test/java/com/eed3si9n/jarjar/util/IOUtilTest.java
Original file line number Diff line number Diff line change
@@ -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<Path> 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);
}
});
}
}


}

0 comments on commit 519cc62

Please # to comment.