Search This Blog

Loading...

Saturday, February 11, 2012

Java: Copy jar files programmatically into another destination

Copying a jar file programmatically involves the following steps:
  1. Create a JarOutputStream based on the destination jar file.
  2.  Loop through all entries in the source jar file, and get the InputStream from each entry.
  3. Create a new jar entry with the same name as the source jar entry, and put the new entry to the JarOutputStream.
  4. Now we have InputStream, OutputStream, and the new jar entry in place, we are ready to transfer the bits, by reading bytes from InputStream into a buffer and writing buffer content to OutputStream.
  5. Close the InputStream, flush the JarOutputStream, and close the jar entry. This completes the copying of one jar entry.
  6. After the loop ends, all entries are copied from source jar file to destination jar file. Finally, close the JarOutputStream.
package test;
import java.io.*;
import java.util.Enumeration;
import java.util.jar.*;

//java -cp . test.CopyZip xxx/lib/sac-1.3.jar /tmp

public class CopyZip {
   public static void main(String[] args) throws Exception {
       File sourceFileOrDir = new File(args[0]);
       File destDir = new File(args[1]);
       if (sourceFileOrDir.isFile()) {
           copyJarFile(new JarFile(sourceFileOrDir), destDir);
       } else if (sourceFileOrDir.isDirectory()) {
           File[] files = sourceFileOrDir.listFiles(new FilenameFilter() {
               public boolean accept(File dir, String name) {
                   return name.endsWith(".jar");
               }
           });
           for (File f : files) {
               copyJarFile(new JarFile(f), destDir);
           }
       }
   }

   public static void copyJarFile(JarFile jarFile, File destDir) throws IOException {
       String fileName = jarFile.getName();
       String fileNameLastPart = fileName.substring(fileName.lastIndexOf(File.separator));
       File destFile = new File(destDir, fileNameLastPart);

       JarOutputStream jos = new JarOutputStream(new FileOutputStream(destFile));
       Enumeration<JarEntry> entries = jarFile.entries();

       while (entries.hasMoreElements()) {
           JarEntry entry = entries.nextElement();
           InputStream is = jarFile.getInputStream(entry);

           //jos.putNextEntry(entry);
           //create a new entry to avoid ZipException: invalid entry compressed size
           jos.putNextEntry(new JarEntry(entry.getName()));
           byte[] buffer = new byte[4096];
           int bytesRead = 0;
           while ((bytesRead = is.read(buffer)) != -1) {
               jos.write(buffer, 0, bytesRead);
           }
           is.close();
           jos.flush();
           jos.closeEntry();
       }
       jos.close();
   }
}

A common error is:

Exception in thread "main" java.util.zip.ZipException: invalid entry compressed
                                                       size (expected 1665 but got 1680 bytes)
   at java.util.zip.ZipOutputStream.closeEntry(ZipOutputStream.java:206)
   at test.CopyZip.copyJarFile(CopyZip.java:63)
   at test.CopyZip.main(CopyZip.java:37)

It usually occurs when text file entries in the source jar file contain some non-ASCII characters such as ^I ^Z ^D ^C. Some common files are META-INF/COPYRIGHT.html, META-INF/LICENSE.txt, etc, probably because these files were created in a non-ASCII editor but saved as text files.

To avoid this type of
ZipException, always create a new JarEntry with the same name, and pass it to putNextEntry() method. Do not pass the existing jar entry from the source jar file to putNextEntry()
method.