0

I've searched high and low for a way to do this, finding all manner of different ways to approach this problem, and yet none of them have worked for me.

I simply need a way to append files to a Jar File, overwriting if they already exist, and I want the fastest way to do this too. I've tried converting it to a Zip File and adding the files using Zip File System but I run into errors such as "zip END header not found", other methods work but are painfully slow (6 MB of files took 3 minutes).

What is the fastest way to append files to a Jar File?

Edit: Yeah go ahead, downvote instead of answering, very productive.

  • 1
    Could you specify what methods you have tried that take too long? – Matt Jul 31 '17 at 18:16
  • I can't recall exactly, but I used a file stream to extract the contents of the Jar File. I think it was similar to the top answer to this https://stackoverflow.com/questions/1529611/how-to-write-a-java-program-which-can-extract-a-jar-file-and-store-its-data-in-s I then added files using some other methods and renamed the whole thing to .jar – QuantumVertex Jul 31 '17 at 18:21
  • You can use any ZIP processing library, there are tons of. I would go for truezip – Marged Jul 31 '17 at 18:34
  • Yeah but that means I still need to get the contents out of the Jar File and add my own files to it right? I want a way to do all of this quickly, hence why it would be nice if I didn't have to unpack, add files, repack and rename and could instead do it in fewer steps. – QuantumVertex Jul 31 '17 at 18:50

2 Answers2

2

You'd do this the same way you update a text file:

  • Open an input stream from current file.
  • Open an output stream to new (temporary) file.
  • Read from input stream:
    • If no change needed, write to output stream.
    • If content should be removed, don't write it.
    • If content should be changed, change it, then write it.
    • If new content should be added at this point, write it now.
    • Repeat until all content processed.
  • Close both streams.
  • Delete/rename old file and rename new file to old file.

In your case, that means:

  • Start with list of files to be added.
  • Open ZipInputStream on current Jar file.
  • Open ZipOutputStream on new (temporary) Jar file.
  • Copy all existing entries (files), except files that will be replaced.
  • Insert all new files.
  • Close both streams.
  • Delete/rename old Jar file and rename new file to old Jar file.

You can of course flip the order.

  • Open ZipOutputStream on new (temporary) Jar file.
  • Insert all new files, and remember their names.
  • Open ZipInputStream on current Jar file.
  • Copy all existing entries (files), except files already added.
  • Close both streams.
  • Delete/rename old Jar file and rename new file to old Jar file.
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • How would I go about copying the existing files **except** the ones that will be replaced? Streams copy stuff in bytes (from my understanding), so how do I recognise when a whole file has been copied and get its name? @Andreas – QuantumVertex Jul 31 '17 at 18:58
  • Suspicions have been confirmed: https://stackoverflow.com/questions/4930111/get-file-name-from-fileoutputstream I can't copy all the files except for the replaced ones using streams. – QuantumVertex Jul 31 '17 at 19:05
  • @QuantumVertex What does that link have to do with anything? Did you even look at how `ZipInputStream` works? Perhaps if you read the documentation of it, you'd realize that what I described is entirely possible, since the file names of zip *entries* are **given to you**. – Andreas Jul 31 '17 at 19:15
  • I see. Will give it a shot. – QuantumVertex Jul 31 '17 at 19:19
  • This seems like a very long and complicated way of copying files, judging by the SO questions I've been looking at, is there no simpler way of doing it? @Andreas – QuantumVertex Jul 31 '17 at 19:32
  • @QuantumVertex Not with standard Java classes. There might be 3rd-party libraries, that could do it easier. If you search for them, you might find one. Don't ask us to do that, since [questions asking us to recommend or find a software library are off-topic for Stack Overflow](https://stackoverflow.com/help/on-topic). – Andreas Jul 31 '17 at 19:53
  • Well what was that Zip File System stuff? I got an error with that before but it's a standard Java class and apparently doesn't require you to create 2 files. @Andreas – QuantumVertex Jul 31 '17 at 19:56
  • @QuantumVertex You said you tried the *Zip File System* and it didn't work. I haven't tried that for the purpose you have, so I took your statement at face value, and gave you a solution that will work. If you think it didn't work because you did it wrong, then show the code of how you did it, otherwise how would we be able to tell you what you did wrong? – Andreas Jul 31 '17 at 19:59
0

Here is the best way I could found:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

public class JarUtil {

    public static void updateJarFile(File srcJarFile, boolean update, File ...filesToAdd) throws IOException {

        File tmpJarFile = File.createTempFile("tempJar", ".tmp");
        JarFile jarFile = new JarFile(srcJarFile);
        boolean jarUpdated = false;
        List<String> fileNames = new ArrayList<String>();

        try {
            JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(tmpJarFile));
            try {
                // Added the new files to the jar.
                for (int i = 0; i < filesToAdd.length; i++) {
                    File file = filesToAdd[i];
                    FileInputStream fis = new FileInputStream(file);
                    try {
                        byte[] buffer = new byte[1024];
                        int bytesRead = 0;
                        JarEntry entry = new JarEntry(file.getName());
                        fileNames.add(entry.getName());
                        tempJarOutputStream.putNextEntry(entry);
                        while ((bytesRead = fis.read(buffer)) != -1) {
                            tempJarOutputStream.write(buffer, 0, bytesRead);
                        }

                        // System.out.println(entry.getName() + " added.");
                    } finally {
                        fis.close();
                    }
                }

                // Copy original jar file to the temporary one.
                Enumeration<?> jarEntries = jarFile.entries();
                while (jarEntries.hasMoreElements()) {
                    JarEntry entry = (JarEntry) jarEntries.nextElement();
                    /*
                     * Ignore classes from the original jar which are being
                     * replaced
                     */
                    String[] fileNameArray = (String[]) fileNames
                            .toArray(new String[0]);
                    Arrays.sort(fileNameArray);// required for binary search
                    if (Arrays.binarySearch(fileNameArray, entry.getName()) < 0) {
                        InputStream entryInputStream = jarFile
                                .getInputStream(entry);
                        tempJarOutputStream.putNextEntry(entry);
                        byte[] buffer = new byte[1024];
                        int bytesRead = 0;
                        while ((bytesRead = entryInputStream.read(buffer)) != -1) {
                            tempJarOutputStream.write(buffer, 0, bytesRead);
                        }
                    } else if (!update) {
                        throw new IOException(
                                "Jar Update Aborted: Entry "
                                        + entry.getName()
                                        + " could not be added to the jar"
                                        + " file because it already exists and the update parameter was false");
                    }
                }

                jarUpdated = true;
            } catch (Exception ex) {
                System.err.println("Unable to update jar file");
                tempJarOutputStream.putNextEntry(new JarEntry("stub"));
            } finally {
                tempJarOutputStream.close();
            }

        } finally {
            jarFile.close();
            // System.out.println(srcJarFile.getAbsolutePath() + " closed.");

            if (!jarUpdated) {
                tmpJarFile.delete();
            }
        }

        if (jarUpdated) {
            srcJarFile.delete();
            tmpJarFile.renameTo(srcJarFile);
            // System.out.println(srcJarFile.getAbsolutePath() + " updated.");
        }
    }
}

Original Source (modified): https://subversivebytes.wordpress.com/2012/10/11/java-programmatically-update-jar-file/ Hope this helps.

Ibrahim.H
  • 1,062
  • 1
  • 13
  • 22