web-dev-qa-db-fra.com

Comment lister les fichiers dans un fichier JAR?

J'ai ce code qui lit tous les fichiers d'un répertoire.

    File textFolder = new File("text_directory");

    File [] texFiles = textFolder.listFiles( new FileFilter() {
           public boolean accept( File file ) {
               return file.getName().endsWith(".txt");
           }
    });

Cela fonctionne très bien. Il remplit le tableau avec tous les fichiers qui se terminent par ".txt" du répertoire "text_directory".

Comment puis-je lire le contenu d'un répertoire de la même manière dans un fichier JAR?

Donc ce que je veux vraiment faire, c'est lister toutes les images dans mon fichier JAR, pour pouvoir les charger avec:

ImageIO.read(this.getClass().getResource("CompanyLogo.png"));

(Celui-ci fonctionne parce que "CompanyLogo" est "codé en dur" mais le nombre d'images dans le fichier JAR pourrait être de 10 à 200 longueurs variables.)

EDIT

Je suppose donc que mon problème principal serait: Comment connaître le nom du fichier JAR où habite ma classe principale?

D'accord, je pourrais le lire en utilisant Java.util.Zip.

Ma structure est comme ça:

Ils sont comme:

my.jar!/Main.class
my.jar!/Aux.class
my.jar!/Other.class
my.jar!/images/image01.png
my.jar!/images/image02a.png
my.jar!/images/imwge034.png
my.jar!/images/imagAe01q.png
my.jar!/META-INF/manifest 

Pour le moment, je peux par exemple charger "images/image01.png" en utilisant:

    ImageIO.read(this.getClass().getResource("images/image01.png));

Mais uniquement parce que je connais le nom du fichier, je dois les charger dynamiquement pour le reste.

106
OscarRyz
CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
if (src != null) {
  URL jar = src.getLocation();
  ZipInputStream Zip = new ZipInputStream(jar.openStream());
  while(true) {
    ZipEntry e = Zip.getNextEntry();
    if (e == null)
      break;
    String name = e.getName();
    if (name.startsWith("path/to/your/dir/")) {
      /* Do something with this entry. */
      ...
    }
  }
} 
else {
  /* Fail... */
}

Notez que dans Java 7, vous pouvez créer un FileSystem à partir du fichier JAR (Zip), puis utiliser les mécanismes de recherche et filtrage du répertoire de NIO pour le parcourir. il est plus facile d'écrire du code qui gère les fichiers JAR et les répertoires "éclatés".

84
erickson

Code qui fonctionne pour les fichiers IDE et .jar:

import Java.io.*;
import Java.net.*;
import Java.nio.file.*;
import Java.util.*;
import Java.util.stream.*;

public class ResourceWalker {
    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        Path myPath;
        if (uri.getScheme().equals("jar")) {
            FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap());
            myPath = fileSystem.getPath("/resources");
        } else {
            myPath = Paths.get(uri);
        }
        Stream<Path> walk = Files.walk(myPath, 1);
        for (Iterator<Path> it = walk.iterator(); it.hasNext();){
            System.out.println(it.next());
        }
    }
}
66
acheron55

erickson's answer a parfaitement fonctionné:

Voici le code de travail.

CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
List<String> list = new ArrayList<String>();

if( src != null ) {
    URL jar = src.getLocation();
    ZipInputStream Zip = new ZipInputStream( jar.openStream());
    ZipEntry ze = null;

    while( ( ze = Zip.getNextEntry() ) != null ) {
        String entryName = ze.getName();
        if( entryName.startsWith("images") &&  entryName.endsWith(".png") ) {
            list.add( entryName  );
        }
    }

 }
 webimages = list.toArray( new String[ list.size() ] );

Et je viens de modifier ma méthode de chargement à partir de ceci:

File[] webimages = ... 
BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex].getName() ));

Pour ça:

String  [] webimages = ...

BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));
20
OscarRyz

Je voudrais développer acheron55 réponse , car c'est une solution très peu sûre, pour plusieurs raisons:

  1. Il ne ferme pas l'objet FileSystem.
  2. Il ne vérifie pas si l'objet FileSystem existe déjà.
  3. Ce n'est pas thread-safe.

C'est une solution un peu plus sûre:

private static ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();

public void walk(String path) throws Exception {

    URI uri = getClass().getResource(path).toURI();
    if ("jar".equals(uri.getScheme()) {
        safeWalkJar(path, uri);
    } else {
        Files.walk(Paths.get(path));
    }
}

private void safeWalkJar(String path, URI uri) throws Exception {

    synchronized (getLock(uri)) {    
        // this'll close the FileSystem object at the end
        try (FileSystem fs = getFileSystem(uri)) {
            Files.walk(fs.getPath(path));
        }
    }
}

private Object getLock(URI uri) {

    String fileName = parseFileName(uri);  
    locks.computeIfAbsent(fileName, s -> new Object());
    return locks.get(fileName);
}

private String parseFileName(URI uri) {

    String schemeSpecificPart = uri.getSchemeSpecificPart();
    return schemeSpecificPart.substring(0, schemeSpecificPart.indexOf("!"));
}

private FileSystem getFileSystem(URI uri) throws IOException {

    try {
        return FileSystems.getFileSystem(uri);
    } catch (FileSystemNotFoundException e) {
        return FileSystems.newFileSystem(uri, Collections.<String, String>emptyMap());
    }
}   

Il n'y a pas vraiment besoin de synchroniser sur le nom du fichier; on pourrait simplement synchroniser sur le même objet à chaque fois (ou faire la méthode synchronized), c'est purement une optimisation.

Je dirais que c'est toujours une solution problématique, car il se peut que d'autres parties du code utilisent l'interface FileSystem sur les mêmes fichiers, et que cela puisse interférer avec eux (même dans une seule application threadée).
En outre, il ne vérifie pas nulls (par exemple, sur getClass().getResource()].

Cette interface Java NIO est assez horrible, car elle introduit une ressource globale/singleton non sécurisée pour les threads, et sa documentation est extrêmement vague (beaucoup d’inconnues dues à des implémentations spécifiques à un fournisseur). Les résultats peuvent varier pour les autres fournisseurs FileSystem (pas JAR) Peut-être qu'il y a une bonne raison à cela: je ne sais pas, je n'ai pas fait de recherche sur les implémentations.

8
Eyal Roth

Je suppose donc que mon problème principal serait de savoir comment connaître le nom du bocal où vit ma classe principale.

En supposant que votre projet soit compressé dans un fichier Jar (pas nécessairement vrai!), Vous pouvez utiliser ClassLoader.getResource () ou findResource () avec le nom de la classe (suivi de .class) pour obtenir le fichier jar contenant une classe donnée. Vous devrez analyser le nom du jar à partir de l'URL renvoyée (pas si difficile), que je laisserai comme exercice au lecteur :-)

Assurez-vous de tester le cas où la classe ne fait pas partie d'un bocal.

6
Kevin Day

Voici une méthode que j'ai écrite pour "exécuter tous les JUnits sous un paquet". Vous devriez être capable de l'adapter à vos besoins.

private static void findClassesInJar(List<String> classFiles, String path) throws IOException {
    final String[] parts = path.split("\\Q.jar\\\\E");
    if (parts.length == 2) {
        String jarFilename = parts[0] + ".jar";
        String relativePath = parts[1].replace(File.separatorChar, '/');
        JarFile jarFile = new JarFile(jarFilename);
        final Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            final JarEntry entry = entries.nextElement();
            final String entryName = entry.getName();
            if (entryName.startsWith(relativePath)) {
                classFiles.add(entryName.replace('/', File.separatorChar));
            }
        }
    }
}

Edit: Ah, dans ce cas, vous voudrez peut-être aussi cet extrait (même cas d'utilisation :))

private static File findClassesDir(Class<?> clazz) {
    try {
        String path = clazz.getProtectionDomain().getCodeSource().getLocation().getFile();
        final String codeSourcePath = URLDecoder.decode(path, "UTF-8");
        final String thisClassPath = new File(codeSourcePath, clazz.getPackage().getName().repalce('.', File.separatorChar));
    } catch (UnsupportedEncodingException e) {
        throw new AssertionError("impossible", e);
    }
}
5
Ran Biron

Voici un exemple d'utilisation de la bibliothèque Reflections pour analyser récursivement le chemin d'accès aux classes à l'aide d'un motif de nom d'expression régulière agrémenté de deux guava avantages de récupérer le contenu des ressources:

Reflections reflections = new Reflections("com.example.package", new ResourcesScanner());
Set<String> paths = reflections.getResources(Pattern.compile(".*\\.template$"));

Map<String, String> templates = new LinkedHashMap<>();
for (String path : paths) {
    log.info("Found " + path);
    String templateName = Files.getNameWithoutExtension(path);
    URL resource = getClass().getClassLoader().getResource(path);
    String text = Resources.toString(resource, StandardCharsets.UTF_8);
    templates.put(templateName, text);
}

Cela fonctionne avec les bocaux et les classes éclatées.

4
Vadzim

J'ai porté réponse de acheron55 à Java 7 et a fermé l'objet FileSystem. Ce code fonctionne dans l'IDE, dans des fichiers jar et dans un jar à l'intérieur une guerre sur Tomcat 7, mais notez que cela ne fonctionne pas dans un bocal à l’intérieur d’une guerre sur JBoss 7 (cela donne FileSystemNotFoundException: Provider "vfs" not installed, voir aussi ce post ). De plus, comme le code d'origine, il n'est pas thread-safe, comme suggéré par errr . Pour ces raisons, j'ai abandonné cette solution. Cependant, si vous pouvez accepter ces problèmes, voici mon code clé en main:

import Java.io.IOException;
import Java.net.*;
import Java.nio.file.*;
import Java.nio.file.attribute.BasicFileAttributes;
import Java.util.Collections;

public class ResourceWalker {

    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        System.out.println("Starting from: " + uri);
        try (FileSystem fileSystem = (uri.getScheme().equals("jar") ? FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap()) : null)) {
            Path myPath = Paths.get(uri);
            Files.walkFileTree(myPath, new SimpleFileVisitor<Path>() { 
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    System.out.println(file);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }
}
3
Pino

Il y a quelque temps, j'ai créé une fonction qui obtient les classes de JAR:

public static Class[] getClasses(String packageName) 
throws ClassNotFoundException{
    ArrayList<Class> classes = new ArrayList<Class> ();

    packageName = packageName.replaceAll("\\." , "/");
    File f = new File(jarName);
    if(f.exists()){
        try{
            JarInputStream jarFile = new JarInputStream(
                    new FileInputStream (jarName));
            JarEntry jarEntry;

            while(true) {
                jarEntry=jarFile.getNextJarEntry ();
                if(jarEntry == null){
                    break;
                }
                if((jarEntry.getName ().startsWith (packageName)) &&
                        (jarEntry.getName ().endsWith (".class")) ) {
                    classes.add(Class.forName(jarEntry.getName().
                            replaceAll("/", "\\.").
                            substring(0, jarEntry.getName().length() - 6)));
                }
            }
        }
        catch( Exception e){
            e.printStackTrace ();
        }
        Class[] classesA = new Class[classes.size()];
        classes.toArray(classesA);
        return classesA;
    }else
        return null;
}
3
berni

Un fichier JAR est juste un fichier Zip avec un manifeste structuré. Vous pouvez ouvrir le fichier jar avec les outils habituels Java Zip et analyser le contenu du fichier de cette façon, gonfler les flux, etc.). Utilisez-le ensuite dans un appel getResourceAsStream.

EDIT/après clarification

Il m'a fallu une minute pour me souvenir de tous les détails et je suis sûr qu'il existe des moyens plus propres de le faire, mais je voulais voir que je n'étais pas fou. Dans mon projet, image.jpg est un fichier situé dans une partie du fichier jar principal. Je récupère le chargeur de classe de la classe principale (SomeClass est le point d’entrée) et l’utilise pour découvrir la ressource image.jpg. Ensuite, un peu de magie de flux pour l’intégrer dans cette chose ImageInputStream et tout va bien.

InputStream inputStream = SomeClass.class.getClassLoader().getResourceAsStream("image.jpg");
JPEGImageReaderSpi imageReaderSpi = new JPEGImageReaderSpi();
ImageReader ir = imageReaderSpi.createReaderInstance();
ImageInputStream iis = new MemoryCacheImageInputStream(inputStream);
ir.setInput(iis);
....
ir.read(0); //will hand us a buffered image
3
Mikeb

Avec un fichier JAR réel, vous pouvez lister le contenu en utilisant JarFile.entries(). Vous aurez toutefois besoin de connaître l'emplacement du fichier JAR. Vous ne pouvez pas simplement demander au chargeur de classe de répertorier tout ce à quoi il pourrait accéder.

Vous devriez pouvoir déterminer l'emplacement du fichier JAR en fonction de l'URL renvoyée par ThisClassName.class.getResource("ThisClassName.class"), mais il se peut que ce soit un peu délicat.

3
Jon Skeet
1
jgran
public static ArrayList<String> listItems(String path) throws Exception{
    InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(path);
    byte[] b = new byte[in.available()];
    in.read(b);
    String data = new String(b);
    String[] s = data.split("\n");
    List<String> a = Arrays.asList(s);
    ArrayList<String> m = new ArrayList<>(a);
    return m;
}
1
Felix G.

Juste une manière différente de lister/lire les fichiers à partir d'une URL de jar et cela se fait de manière récursive pour les jars imbriqués

https://Gist.github.com/trung/2cd90faab7f75b3bcbaa

URL urlResource = Thead.currentThread().getContextClassLoader().getResource("foo");
JarReader.read(urlResource, new InputStreamCallback() {
    @Override
    public void onFile(String name, InputStream is) throws IOException {
        // got file name and content stream 
    }
});
0
trung