4

I have a jar or war.

I'm programmaticaly reading this jar, and when I find jar inside this jar I'd like to programmaticaly read it again.

But JarFile provides only getInputStream, which I cannot pass to JarFile(File file) constructor.

How to read jar from jar?

EDIT: I was thinking about getting the File somehow from classloader or so.

blefesd
  • 253
  • 1
  • 3
  • 10
  • While there's nothing stopping you, jars shouldn't contain other jars. I would look for another way to implement the functionality you are trying to achieve. http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html – Nick Holt Jul 24 '09 at 10:39
  • @Nick Eclipse plugins are a counter example, they are delivered as jars, and often contain nested jars – Rich Seller Jul 24 '09 at 11:19
  • @Nick Holt: IMO, if you are allowed to distribute the other JARs in your JAR, you should. I think it makes managing dependencies easier because you can give someone your JAR and they have everything in one file. – Thomas Owens Jul 24 '09 at 16:51

4 Answers4

7

Update: Sorry this is probably too late for your needs, I just spotted your last question in the comments though. So I've modified the example to show each nested entry being copied directly to an OutputStream without any need to inflate the outer jar.

In this case the OutputStream is System.out but it could be any OutputStream (e.g. to a file...).


There's no need to use a temporary file. You can use JarInputStream instead of JarFile, pass the InputStream from the outer entry to the constructor and you can then read the contents of the jar.

For example:

JarFile jarFile = new JarFile(warFile);

Enumeration entries = jarFile.entries();

while (entries.hasMoreElements()) {
    JarEntry jarEntry = (JarEntry) entries.nextElement();

    if (jarEntry.getName().endsWith(".jar")) {
        JarInputStream jarIS = new JarInputStream(jarFile
                .getInputStream(jarEntry));

        // iterate the entries, copying the contents of each nested file 
        // to the OutputStream
        JarEntry innerEntry = jarIS.getNextJarEntry();

        OutputStream out = System.out;

        while (innerEntry != null) {
            copyStream(jarIS, out, innerEntry);
            innerEntry = jarIS.getNextJarEntry();
        }
    }
}

...

/**
 * Read all the bytes for the current entry from the input to the output.
 */
private void copyStream(InputStream in, OutputStream out, JarEntry entry)
        throws IOException {
    byte[] buffer = new byte[1024 * 4];
    long count = 0;
    int n = 0;
    long size = entry.getSize();
    while (-1 != (n = in.read(buffer)) && count < size) {
        out.write(buffer, 0, n);
        count += n;
    }
}
Rich Seller
  • 83,208
  • 23
  • 172
  • 177
  • I think you cannot process JarEntry as ZipFile and get entries() and work with it. – blefesd Jul 24 '09 at 16:00
  • Try it, it works. A jar is just a zip with a different extension and a convention that it has a manifest. – Rich Seller Jul 24 '09 at 16:19
  • but from JarEntry or ZipEntry you cannot obtain all entries in included jar.Like this: JarFile jf = new JarFile(jarName); Enumeration en = jf.entries(); I needed to recursively walk the jars and search in those jars. From JarEntry I cannot get entries(); Or at least I haven't found how. – blefesd Jul 27 '09 at 07:17
  • that's what the last part of the second snippet does isn't it? or have I missed something? – Rich Seller Jul 27 '09 at 07:23
  • if you're looking for a means to identify the entries of a nested jar without iterating/recursing, I don't think there is any such method. The last line of the snippet above gives you an Enumeration of the inner entries, this is equivalent to calling entries() on a JarFile – Rich Seller Jul 27 '09 at 07:27
  • Hi, I might be missing something, I'm really able to get the inner entries, but I don't know how to write this inner entry(file) to the e.g. stdout. – blefesd Jul 30 '09 at 07:49
  • I tried the code. The size of innerEntry is -1. Therefore I can't write the correct number of bytes to the output stream!? – Michael K. Sep 05 '13 at 13:40
  • I must correct my comment. There is a valid size of innerEntry. Only my eclipse-debugger showed me -1. – Michael K. Sep 09 '13 at 06:58
1

you can create jar file in File System, something like

 File tempFile=TempFile.createFile("newJar",".jar");

and write Stream into it. After that you can construct your JarFile(tempFile) and handle it...

Forget about it if program is running as unsigned applet/JNLP since you will not have right to create file in file system...

ante.sabo
  • 3,141
  • 6
  • 28
  • 36
  • yes this works, but I'm not sure if it's the best to write temp file somewhere. and if there is not any other solution without creating temp file – blefesd Jul 24 '09 at 16:14
  • 1
    don't know; file has to be in file system (don't beleive there is something like InMemoryFile :)) and they've made it that way on purpose you cannot instance JarFile without passing file inside.. – ante.sabo Jul 27 '09 at 05:32
0

Generally it is a matter of getting to an InputStream and then work with that. This allows you to abstract from most "jar in jar on web server" issues and similar.

Apparently in this case it is JarFile.getInputStream() and JarInputStream().

Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
-1

Recursively use the JarFile again to read the new jar file. For ex.,

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;


public class JarReader {

public static void readJar(String jarName) throws IOException {
    String dir = new File(jarName).getParent();
    JarFile jf = new JarFile(jarName);
    Enumeration<JarEntry> en = jf.entries();
    while(en.hasMoreElements()) { 
        ZipEntry ze = (ZipEntry)en.nextElement();
        if(ze.getName().endsWith(".jar")) { 
            readJar(dir + System.getProperty("file.separator") + ze.getName());
        } else {
            InputStream is = jf.getInputStream(ze);
            // ... read from input stream
            is.close();
            System.out.println("Processed class: " + ze.getName());
        }   
    }
}

public static void main(String[] args) throws IOException {
    readJar(args[0]);
}

}
Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
prasadvk
  • 1,568
  • 2
  • 13
  • 13
  • 1
    This doesn't work JarFile jf = new JarFile(jarName); JarFile expect the jarName(file) on the system. And you will get java.util.zip.ZipException: The system cannot find the path specified – blefesd Jul 24 '09 at 15:30