2009-08-15 3 views
49

Wie erstellt man eine JAR-Datei programmgesteuert mit java.util.jar.JarOutputStream? Die JAR-Datei, die von meinem Programm erzeugt wird, sieht korrekt aus (es extrahiert gut), aber wenn ich versuche, eine Bibliothek von ihr zu laden, beschwert sich Java, dass es keine Dateien finden kann, die klar darin gespeichert sind. Wenn ich die JAR-Datei entpacke und das Befehlszeilentool jar von Sun verwende, um die Datei erneut zu komprimieren, funktioniert die resultierende Bibliothek einwandfrei. Kurz gesagt, etwas stimmt nicht mit meiner JAR-Datei.Wie verwende ich JarOutputStream, um eine JAR-Datei zu erstellen?

Erläutern Sie, wie Sie eine JAR-Datei programmgesteuert mit einer Manifestdatei erstellen.

+2

Vielleicht sollten Sie Ihre aktuelle (ohne Funktion) zeigen Lösung – ChssPly76

Antwort

82

Es stellt sich heraus, dass JarOutputStream hat drei undokumentierte Macken:

  1. Verzeichnisnamen muss ein Ende haben mit einem '/' Schrägstrich.
  2. Pfade müssen '/' Schrägstriche verwenden, nicht '\'
  3. Einträge dürfen nicht mit einem '/' Schrägstrich beginnen.

Hier ist der richtige Weg, um eine Jar-Datei zu erstellen:

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

Diese "Macken" sind eigentlich Teil der ZIP-Spezifikation (JAR-Dateien sind nur ZIP-Dateien mit einem Manifest und einer anderen Erweiterung). Ich stimme zu, dass dies in den API-Dokumenten dokumentiert werden sollte. Ich schlage jedoch vor, ein Problem zu beheben (http://bugs.sun.com/bugdatabase/). –

+5

Noch wichtiger: Die API sollte verhindern, dass Sie ungültige ZIP/JAR-Dateien erstellen Ausnahmen werden ausgelöst, wenn Sie den falschen Schrägstrich übergeben oder diese automatisch konvertieren. In Bezug auf Verzeichnisse, die mit einem Schrägstrich enden, sollte dies unbedingt dokumentiert werden, da es keine Möglichkeit gibt, es automatisch zu korrigieren. Ich habe einen Fehlerbericht eingereicht, der jedoch noch nicht akzeptiert wurde. – Gili

+0

Klassische Sonne/Oracle. Geschlossen als "kein Fehler": http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6873352 – Gili

3

Hier einige Beispiel-Code eine JAR-Datei mit dem JarOutputStream für die Erstellung von:

+1

Ich mache das schon. Tatsächlich weist das Beispiel, auf das Sie verwiesen haben, darauf hin, dass manNextEntry() explizit auf Verzeichnisnamen setzen oder JarOutputStream.closeEntry() aufrufen muss. Etwas anderes muss falsch sein. – Gili

+0

Ah, OK. Es war ein wenig schwierig, eine bessere Lösung anzubieten, ohne Code zu sehen, also habe ich Sie gerade auf diese Referenz hingewiesen. Ich bin froh, dass du es herausgefunden hast. – ars

+0

Ich schätze Ihre Hilfe. Vielen Dank! – Gili

9

Es gibt eine andere „Marotte“ zu beachten: Alle Namen der JarEntry sollte nicht mit „/“ beginnen.

Beispiel: Der Name des Jar-Eintrags für die Manifestdatei lautet "META-INF/MANIFEST.MF" und nicht "/META-INF/MANIFEST.MF".

Die gleiche Regel sollte für alle jar-Einträge beachtet werden.

1

Sie können es mit diesem Code tun:

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 Variable: Pfad zur JAR-Datei

FILE Variable: Name und Format

+0

Was fügt dies hinzu, dass die angenommene Antwort nicht schon sagt? – Gili

+0

Die akzeptierte Antwort verwendet mehr Code als meine. Ich denke, du willst nicht sehr viel Code schreiben, wenn du ein bisschen schreiben kannst. – Muskovets

+1

In aller Gerechtigkeit, enthält Ihre Antwort weniger Code, weil es weniger tut. Die akzeptierte Antwort enthält Code, der die Eingabe in das von JarOutputStream erwartete Format mischt. Wenn Sie unter Windows ausführen oder wenn Verzeichnis-Namen nicht mit einem Schrägstrich enden, wird Ihr Code unbemerkt fehlschlagen. – Gili