2009-08-15 20 views
49

Jak utworzyć plik JAR programowo za pomocą java.util.jar.JarOutputStream? Plik JAR wygenerowany przez mój program wygląda poprawnie (wydobywa się dobrze), ale kiedy próbuję załadować bibliotekę z tego, Java narzeka, że ​​nie może znaleźć plików, które są w niej wyraźnie zapisane. Jeśli wyodrębnię plik JAR i użyję narzędzia wiersza polecenia Sun, aby ponownie skompresować, biblioteka wynikowa działa poprawnie. W skrócie, coś jest nie tak z moim plikiem JAR.Jak użyć JarOutputStream do utworzenia pliku JAR?

Proszę wyjaśnić, jak utworzyć plik JAR programowo, wraz z plikiem manifestu.

+2

Może powinieneś pokazać swoją aktualną (wolne od pracy) rozwiązanie – ChssPly76

Odpowiedz

82

Okazuje się, że JarOutputStream ma trzy nieudokumentowanych dziwactwa:

  1. Nazwy katalogów musi kończyć z ukośnikiem "/".
  2. Ścieżki muszą używać ukośników "/", a nie "\"
  3. Wpisy nie mogą zaczynać się od ukośnika "/".

Oto poprawny sposób, aby utworzyć plik słoika:

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

te "dziwactwa" są w rzeczywistości częścią specyfikacji zip (pliki jar są po prostu plikami zip z manifestem i innym rozszerzeniem). Zgadzam się, że powinno to być udokumentowane w dokumentach API - sugeruję otwarcie problemu (http://bugs.sun.com/bugdatabase/) –

+5

Co ważniejsze, API powinno uniemożliwić tworzenie nieprawidłowych plików ZIP/JAR przez rzucanie wyjątków, jeśli wprowadzisz niewłaściwy typ ukośnika lub automatycznie je konwertujesz. W odniesieniu do katalogów kończących się ukośnikiem, zdecydowanie powinno być udokumentowane, ponieważ nie ma sposobu, aby skorygować je automatycznie. Złożyłem zgłoszenie błędu, ale nie zostało to jeszcze zaakceptowane. – Gili

+0

Classic Sun/Oracle. Zamknięte jako "not a bug": http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6873352 – Gili

3

Oto przykładowy kod do tworzenia pliku JAR pomocą JarOutputStream:

+1

Już to robię. W rzeczywistości przykład, do którego się odwołujesz, nie wskazuje, że należy jawnie umieścićNextEntry() na nazwach katalogów lub wywołać JarOutputStream.closeEntry(). Coś jeszcze musi być nie tak. – Gili

+0

Ah, OK. To było trochę trudne, aby zaoferować lepsze rozwiązanie, nie widząc żadnego kodu, więc po prostu wskazałem ci ten odnośnik. Cieszę się, że to wymyśliłeś. – ars

+0

Doceniam twoją pomoc. Dziękuję Ci! – Gili

9

Jest jeszcze jeden „dziwactwo”, aby zwrócić uwagę: Wszystkie nazwy JarEntry nie powinien zaczynać się od „/”.

Na przykład: Nazwa pliku JAR dla pliku manifestu to "META-INF/MANIFEST.MF", a nie "/META-INF/MANIFEST.MF".

Ta sama reguła powinna być stosowana dla wszystkich wpisów w jar.

1

Można to zrobić z tym kodem:

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 zmienna: ścieżka do pliku JAR

FILE zmienna: nazwisko i format

+0

Co to oznacza, że ​​zaakceptowana odpowiedź jeszcze nie mówi? – Gili

+0

Przyjęta odpowiedź używa więcej kodu niż mój. Myślę, że nie chcesz pisać bardzo dużo, kiedy możesz trochę napisać. – Muskovets

+1

W pełni sprawiedliwie, twoja odpowiedź zawiera mniej kodu, ponieważ robi mniej. Przyjęta odpowiedź zawiera kod masujący wejście do formatu oczekiwanego przez JarOutputStream. Twój kod nie powiedzie się po cichu, jeśli uruchomisz system Windows lub nazwy katalogów nie zakończą się ukośnikiem. – Gili

Powiązane problemy