tags:

views:

1596

answers:

3

How does one create a JAR file programmatically using java.util.jar.JarOutputStream? The JAR file produced by my program looks correct (it extracts fine) but when I try loading a library from it Java complains that it cannot find files which are clearly stored inside it. If I extract the JAR file and use Sun's jar command-line tool to re-compress it the resulting library works fine. In short, something is wrong with my JAR file.

Please explain how to create a JAR file programmatically, complete with a manifest file.

+1  A: 

Here's some sample code for creating a JAR file using the JarOutputStream:

ars
I'm already doing this. In fact, the example you referenced to fails to point out that one must explicitly putNextEntry() on directory names or invoke JarOutputStream.closeEntry(). Something else must be wrong.
Gili
Ah, OK. It was a little hard to offer a better solution without seeing any code, so I just pointed you at that reference. Glad you figured it out though.
ars
I appreciate your help. Thank you!
Gili
+5  A: 

Unfreaking believable...

It turns out that JarOutputStream has two undocumented quirks:

  1. Directory names must end with a slash '/'
  2. All paths must use '/' style slashes, not '\'

Here is the correct way to create a Jar file:

public void run() throws IOException
{
  Manifest manifest = new Manifest();
  manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
  JarOutputStream target = new JarOutputStream(new FileOutputStream("output.jar"), manifest);
  add(new File("inputDirectory"), target);
  target.close();
}

private void add(File source, JarOutputStream target) throws IOException
{
  BufferedInputStream in = null;
  try
  {
    if (source.isDirectory())
    {
      String name = source.getPath().replace("\\", "/");
      if (!name.isEmpty())
      {
        if (!name.endsWith("/"))
          name += "/";
        JarEntry entry = new JarEntry(name);
        entry.setTime(source.lastModified());
        target.putNextEntry(entry);
        target.closeEntry();
      }
      for (File nestedFile: source.listFiles())
        add(nestedFile, target);
      return;
    }

    JarEntry entry = new JarEntry(source.getPath().replace("\\", "/"));
    entry.setTime(source.lastModified());
    target.putNextEntry(entry);
    in = new BufferedInputStream(new FileInputStream(source));

    byte[] buffer = new byte[1024];
    while (true)
    {
      int count = in.read(buffer);
      if (count == -1)
        break;
      target.write(buffer, 0, count);
    }
    target.closeEntry();
  }
  finally
  {
    if (in != null)
      in.close();
  }
}
Gili
these 'quirks' are actually part of the zip specification (jar files are just zip files with a manifest and a different extension). I agree that it should be documented in the API docs, though - I suggest opening an issue (http://bugs.sun.com/bugdatabase/)
Kevin Day
More importantly, the API should prevent you from creating invalid ZIP/JAR files by throwing exceptions if you pass in the wrong type of slash or by converting them automatically. With respect to directories ending with a slash, it should definitely be documented since there is no way to correct it automatically. I filed a bug report but it hasn't been accepted yet.
Gili
A: 

There's another "quirk" to pay attention: All JarEntry's names should NOT begin with "/".

For example: The jar entry name for the manifest file is "META-INF/MANIFEST.MF" and not "/META-INF/MANIFEST.MF".

The same rule should be followed for all jar entries.

Hélio Luiz Alves Rodrigues