62 votes

Comment utiliser JarOutputStream pour créer un fichier JAR ?

Comment crée-t-on un fichier JAR de manière programmatique en utilisant java.util.jar.JarOutputStream? Le fichier JAR produit par mon programme semble correct (il s'extrait bien), mais lorsque j'essaie de charger une bibliothèque à partir de celui-ci, Java se plaint qu'il ne peut pas trouver des fichiers qui sont clairement stockés à l'intérieur. Si j'extrait le fichier JAR et utilise l'outil en ligne de commande jar de Sun pour le recompresser, la bibliothèque résultante fonctionne correctement. En bref, quelque chose ne va pas avec mon fichier JAR.

Veuillez expliquer comment créer un fichier JAR de manière programmatique, en incluant un fichier manifeste.

3 votes

Peut-être devriez-vous montrer votre solution actuelle (non fonctionnelle)

106voto

Gili Points 14674

Il s'avère que JarOutputStream a trois bizarreries non documentées :

  1. Les noms de répertoires doivent se terminer par une barre oblique '/'.
  2. Les chemins doivent utiliser des barres obliques '/', et non des '\'
  3. Les entrées ne doivent pas commencer par une barre oblique '/'.

Voici la manière correcte de créer un fichier 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 {
    String name = source.getPath().replace("\\", "/");
    if (source.isDirectory()) {
        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);
        }
    } else {
        JarEntry entry = new JarEntry(name);
        entry.setTime(source.lastModified());
        target.putNextEntry(entry);
        try (BufferedInputStream 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();
        }
    }
}

18 votes

Ces "particularités" font en réalité partie de la spécification zip (les fichiers jar ne sont que des fichiers zip avec un manifeste et une extension différente). Je suis d'accord qu'elles devraient être documentées dans la documentation de l'API, cependant - Je suggère d'ouvrir un problème (bugs.sun.com/bugdatabase).

5 votes

Plus important encore, l'API devrait vous empêcher de créer des fichiers ZIP/JAR invalides en lançant des exceptions si vous passez le mauvais type de barre oblique ou en les convertissant automatiquement. En ce qui concerne les répertoires se terminant par une barre oblique, cela devrait absolument être documenté car il n'y a aucun moyen de le corriger automatiquement. J'ai soumis un rapport de bug mais il n'a pas encore été accepté.

0 votes

Classique Sun/Oracle. Fermé comme "pas un bogue": bugs.sun.com/bugdatabase/view_bug.do?bug_id=6873352

10voto

Il y a un autre "particularité" à laquelle faire attention : tous les noms de JarEntry ne doivent PAS commencer par "/".

Par exemple : le nom de l'entrée du fichier manifest est "META-INF/MANIFEST.MF" et non "/META-INF/MANIFEST.MF".

La même règle doit être suivie pour toutes les entrées de jar.

1 votes

Cela devrait être posté en tant que commentaire à la réponse acceptée car cela ne répond pas à la question principale.

3voto

Erhannis Points 1850

D'accord, donc comme demandé, voici le code de Gili, modifié pour utiliser des chemins relatifs plutôt que absolus. (Remplacez "inputDirectory" par le répertoire de votre choix). Je viens de tester, mais si cela ne fonctionne pas, faites-le moi savoir.

   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);
      File inputDirectory = new File("inputDirectory");
      for (File nestedFile : inputDirectory.listFiles())
         add("", nestedFile, target);
      target.close();
   }

   private void add(String parents, File source, JarOutputStream target) throws IOException
   {
      BufferedInputStream in = null;
      try
      {
         String name = (parents + source.getName()).replace("\\", "/");

         if (source.isDirectory())
         {
            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(name, nestedFile, target);
            return;
         }

         JarEntry entry = new JarEntry(name);
         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();
      }
   }

1voto

Muskovets Points 413

Vous pouvez le faire avec ce code :

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("Écriture du fichier : " + 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 : chemin du fichier JAR

FILE variable : nom et format

1voto

Shehan Dhaleesha Points 122

Cette réponse résoudrait le problème du chemin relatif.

private static void createJar(File source, JarOutputStream target) {
        createJar(source, source, target);
    }

    private static void createJar(File source, File baseDir, JarOutputStream target) {
        BufferedInputStream in = null;

        try {
            if (!source.exists()){
                throw new IOException("Le répertoire source est vide");
            }
            if (source.isDirectory()) {
                // Pour les entrées Jar, tous les séparateurs de chemin doivent être '/' (indépendant de l'OS)
                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()) {
                    createJar(nestedFile, baseDir, target);
                }
                return;
            }

            String entryName = baseDir.toPath().relativize(source.toPath()).toFile().getPath().replace("\\", "/");
            JarEntry entry = new JarEntry(entryName);
            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();
        } catch (Exception ignored) {

        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception ignored) {
                    throw new RuntimeException(ignored);
                }
            }
        }
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X