2009-08-15 11 views
49

Come si crea un file JAR in modo programmatico utilizzando java.util.jar.JarOutputStream? Il file JAR prodotto dal mio programma sembra corretto (estrae bene) ma quando provo a caricare una libreria da esso Java si lamenta che non riesce a trovare file che sono chiaramente memorizzati al suo interno. Se estraggo il file JAR e utilizzo lo strumento da riga di comando jar di Sun per comprimerlo nuovamente, la libreria risultante funziona correttamente. In breve, c'è qualcosa di sbagliato nel mio file JAR.Come utilizzare JarOutputStream per creare un file JAR?

Spiegare come creare un file JAR a livello di codice, completo di file manifest.

+2

Forse si dovrebbe mostrare il vostro attuale (non funzionante) soluzione – ChssPly76

risposta

82

Si scopre che JarOutputStream ha tre peculiarità senza documenti:

  1. nomi delle directory devono concludersi con una barra "/".
  2. I percorsi devono utilizzare le barre "/", non "\"
  3. Le voci non possono iniziare con una barra "/".

Ecco il modo corretto per creare un file 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

questi "quirks" sono in realtà parte della specifica zip (i file jar sono solo file zip con manifest e un'estensione diversa). Sono d'accordo che dovrebbe essere documentato nei documenti API, però - Suggerisco di aprire un problema (http://bugs.sun.com/bugdatabase/) –

+5

Ancora più importante, l'API dovrebbe impedire di creare file ZIP/JAR non validi da lanciando eccezioni se si passa al tipo sbagliato di barra o convertendole automaticamente. Per quanto riguarda le directory che terminano con una barra, dovrebbe essere sicuramente documentato poiché non c'è modo di correggerlo automaticamente. Ho presentato una segnalazione di errore ma non è stata ancora accettata. – Gili

+0

Sole classico/Oracle. Chiuso come "non un bug": http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6873352 – Gili

3

Ecco alcuni esempi di codice per la creazione di un file JAR utilizzando il JarOutputStream:

+1

Lo sto già facendo. In effetti, l'esempio a cui si fa riferimento non riesce a indicare che è necessario inserire esplicitamenteNextEntry() nei nomi delle directory o richiamare JarOutputStream.closeEntry(). Qualcos'altro deve essere sbagliato. – Gili

+0

Ah, OK. È stato un po 'difficile offrire una soluzione migliore senza vedere alcun codice, quindi ti ho semplicemente indicato come riferimento. Sono contento che tu l'abbia capito comunque. – ars

+0

Apprezzo il tuo aiuto. Grazie! – Gili

9

C'è un altro "capriccio" di prestare attenzione: tutti i nomi di JarEntry NON dovrebbero iniziare con "/".

Ad esempio: il nome della voce jar per il file manifest è "META-INF/MANIFEST.MF" e non "/META-INF/MANIFEST.MF".

La stessa regola deve essere seguita per tutte le voci jar.

1

Si può fare con questo codice:

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 variabile: percorso del file JAR

FILE variabile: nome e il formato

+0

Cosa aggiunge questo che la risposta accettata non dice già? – Gili

+0

La risposta accettata utilizza più codice del mio. Penso che tu non voglia scrivere molto codice quando puoi scrivere un po '. – Muskovets

+1

In tutta onestà, la tua risposta contiene meno codice perché fa meno. La risposta accettata contiene codice che massaggia l'input nel formato previsto da JarOutputStream. Il tuo codice fallirà silenziosamente se corri sotto Windows o se i nomi delle directory non terminano con una barra. – Gili

Problemi correlati