2009-08-15 18 views
49

¿Cómo se puede crear un archivo JAR programáticamente usando java.util.jar.JarOutputStream? El archivo JAR producido por mi programa parece correcto (extrae bien) pero cuando intento cargar una biblioteca desde allí, Java se queja de que no puede encontrar los archivos que están claramente almacenados dentro de él. Si extraigo el archivo JAR y uso la herramienta de línea de comandos jar de Sun para volver a comprimirlo, la biblioteca resultante funciona bien. En resumen, algo está mal con mi archivo JAR.¿Cómo usar JarOutputStream para crear un archivo JAR?

Explique cómo crear un archivo JAR mediante programación, completo con un archivo de manifiesto.

+2

Tal vez debería mostrar su actual (que no funciona) solución – ChssPly76

Respuesta

82

Resulta que JarOutputStream tiene tres peculiaridades indocumentados:

  1. nombres de directorio deben acabar con un '/' slash.
  2. Las rutas deben usar barras inclinadas '/', no '\'
  3. Las entradas pueden no comenzar con una barra inclinada '/'.

Aquí es la forma correcta para crear un archivo JAR:

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(); 
    } 
} 
+14

estos 'caprichos' son en realidad parte de la especificación zip (los archivos jar son solo archivos zip con un manifiesto y una extensión diferente). Sin embargo, estoy de acuerdo en que debería estar documentado en los documentos de la API. Sugiero abrir un problema (http://bugs.sun.com/bugdatabase/) –

+5

. Más importante aún, la API debería evitar la creación de archivos ZIP/JAR no válidos por lanzando excepciones si pasas el tipo incorrecto de barra o convirtiéndolas automáticamente. Con respecto a los directorios que terminan con una barra diagonal, definitivamente debe documentarse ya que no hay forma de corregirlo automáticamente. Archivé un informe de error pero aún no se ha aceptado. – Gili

+0

Classic Sun/Oracle. Cerrado como "no es un error": http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6873352 – Gili

3

Aquí hay un código de ejemplo para crear un archivo JAR utilizando el JarOutputStream:

+1

Ya estoy haciendo esto. De hecho, el ejemplo al que hizo referencia no señala que uno debe poner explícitamente aNextEntry() en nombres de directorio o invocar a JarOutputStream.closeEntry(). Algo más debe estar mal. – Gili

+0

Ah, está bien. Fue un poco difícil ofrecer una mejor solución sin ver ningún código, así que simplemente te señalé a esa referencia. Me alegro de que lo hayas descifrado. – ars

+0

Agradezco tu ayuda. ¡Gracias! – Gili

9

Hay otro "capricho" que prestar atención: Todos los nombres de JarEntry NO deben comenzar con "/".

Por ejemplo: El nombre de entrada del archivo de manifiesto es "META-INF/MANIFEST.MF" y no "/META-INF/MANIFEST.MF".

Se debe seguir la misma regla para todas las entradas de jar.

1

Puede hacerlo con este código:

public void write(File[] files, String comment) throws IOException { 
    FileOutputStream fos = new FileOutputStream(PATH + FILE); 
    JarOutputStream jos = new JarOutputStream(fos, manifest); 
    BufferedOutputStream bos = new BufferedOutputStream(jos); 
    jos.setComment(comment); 
    for (File f : files) { 
     print("Writing file: " + f.toString()); 
     BufferedReader br = new BufferedReader(new FileReader(f)); 
     jos.putNextEntry(new JarEntry(f.getName())); 
     int c; 
     while ((c = br.read()) != -1) { 
      bos.write(c); 
     } 
     br.close(); 
     bos.flush(); 
    } 
    bos.close(); 
// JarOutputStream jor = new JarOutputStream(new FileOutputStream(PATH + FILE), manifest); 

} 

PATH variables: ruta al archivo JAR

FILE variables: nombre y el formato

+0

¿Qué significa esto que la respuesta aceptada aún no dice? – Gili

+0

La respuesta aceptada usa más código que el mío. Creo que no quieres escribir mucho código cuando puedes escribir un poco. – Muskovets

+1

Con toda la debida equidad, su respuesta contiene menos código porque hace menos. La respuesta aceptada contiene un código que masajea la entrada al formato esperado por JarOutputStream. Su código fallará silenciosamente si se ejecuta en Windows o si los nombres de directorio no terminan con una barra inclinada. – Gili

Cuestiones relacionadas