Creating a fat jar from an existing non-fat jar

Viewed 84

One application I had been using required all classes to be in the same base hierarchy (Meaning jars could not be inside the jar or else it would fail) for whatever reason. Because I was creating my own application for others to use, I had no control over whether or not they had a fat jar or not, so I had to figure out a way to "fatten?" an already made jar.

One could use the JarFile and everytime they encountered a jar file, write the jar into its own file and do it over again; however, that feels like an absolute waste of memory and time to copy stuff over like that just to have access to the JarFile entry list

     Ex: test.jar
         -com.package
             -main.class
         -dependency1.jar
             -com.depedency1
                -test.class

and what was needed:
     test.jar
          -com.package
              -main.class
          -com.dependency1
              -test.class
    
1 Answers

Here is the solution I made for this problem, although it doesn't handle naming collisions that well.

The readJar could be helpful for several other tasks if you want to read all the classes in a jar.

public class JarUtils extends Base {
    public static File fatJar(File dest, File file) throws IOException {
        finer("Generating Fat Jar:" + file);
        try (JarInputStream zis = new JarInputStream(new BufferedInputStream(new FileInputStream(file)));
             JarOutputStream zos = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(dest)), zis.getManifest())) {
            final byte[] buffer = new byte[1024];
            readJar(file.getName(), zis, (entry, is) -> {
                int len;
                zos.putNextEntry(entry);
                while ((len = is.read(buffer)) > 0) {
                    zos.write(buffer, 0, len);
                }
                zos.closeEntry();
            }, new ArrayList<>());
            zos.finish();
        }
        return dest;
    }

    private static void readJar(String jarName, JarInputStream zis, JarEntryWalker walker, List<String> checkedEntries) throws IOException {
        ZipEntry entry;
        if (!jarName.endsWith("/")) jarName += "/";
        while ((entry = zis.getNextEntry()) != null) {
            if (entry.getName().contains(".jar")) {
                readJar(entry.getName(), new JarInputStream(zis), walker, checkedEntries);
            } else {
                String name = (entry.getName().contains("META-INF/") ? jarName : "") + entry.getName();
                if (!checkedEntries.contains(name)) {
                    ZipEntry zipEntry = new ZipEntry(name);
                    zipEntry.setComment(entry.getComment());
                    zipEntry.setExtra(entry.getExtra());
                    zipEntry.setCreationTime(entry.getCreationTime());
                    zipEntry.setLastAccessTime(entry.getLastAccessTime());
                    zipEntry.setLastModifiedTime(entry.getLastModifiedTime());
                    walker.entry(zipEntry, zis);
                    checkedEntries.add(name);
                }
            }
        }
    }
}

interface JarEntryWalker {
    void entry(ZipEntry zipEntry, InputStream is) throws IOException;
}
Related