Comment puis-je 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");
           }
    });

ça marche 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 manière similaire dans un fichier JAR?

alors ce que je veux vraiment faire, c'est lister toutes les images à l'intérieur de mon fichier JAR, pour que je puisse les charger avec:

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

(que l'on travaille parce que le" CompanyLogo "est" hardcoded " mais le nombre d'images à l'intérieur du fichier JAR pourrait être de 10 à 200 longueur variable.)

MODIFIER

donc je suppose que mon principal problème serait: Comment savoir le nom du fichier JAR où vit ma classe principale?

étant donné que je pouvais le lire en utilisant java.util.Zip .

ma Structure est comme ceci:

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 

en ce moment je suis capable de charger par exemple "images/image01.png" utilisant:

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

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

93
demandé sur Erick Robertson 2009-09-15 23:28:40

13 réponses

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 qu'en Java 7, vous pouvez créer un FileSystem à partir du fichier JAR (zip), puis utiliser les mécanismes de recherche et de filtrage du répertoire de NIO. Il serait ainsi plus facile d'écrire du code qui traite les bocaux et les répertoires "explosés".

78
répondu erickson 2016-07-01 20:23:12

Code qui fonctionne à la fois pour IDE et .fichiers 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());
        }
    }
}
56
répondu acheron55 2016-09-18 03:34:18

erickson réponse 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 charge de ceci:

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

à ceci:

String  [] webimages = ...

BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));
19
répondu OscarRyz 2017-05-23 12:17:53

je voudrais développer sur acheron55 réponse , car il s'agit d'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 sans fil.

C'est un peu une solution 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 y a aucun besoin réel 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 qu'il s'agit toujours d'une solution problématique, car il pourrait y avoir d'autres parties du code qui utilisent l'interface FileSystem sur les mêmes fichiers, et cela pourrait interférer avec eux (même dans une seule application filetée).

Aussi, il n'a pas vérifier null s (par exemple, sur getClass().getResource() .

cette interface Java NIO particulière est un peu horrible, car elle introduit une ressource globale/singleton non thread-safe, et sa documentation est extrêmement vague (beaucoup d'inconnues en raison d'implémentations spécifiques au fournisseur). Les résultats peuvent varier pour d'autres fournisseurs FileSystem (pas JAR). Peut-être qu'il y a une bonne raison pour que ce soit ainsi; Je ne sais pas, je n'ai pas fait de recherches sur les implémentations.

6
répondu Eyal Roth 2017-05-23 12:34:09

Donc je suppose que mon principal problème, comment savoir le nom de la jarre où ma classe principale vies.

en supposant que votre projet est emballé dans un bocal (pas nécessairement vrai!), vous pouvez utiliser ClassLoader.getResource() ou findResource() avec le nom de la classe suivi .classe) pour obtenir le pot qui contient une classe donnée. Vous devrez analyser le nom du jar à partir de L'URL qui sera retournée (pas si difficile), que je vais laisser comme exercice pour le lecteur :-)

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

5
répondu Kevin Day 2009-09-16 06:41:30

Voici une méthode que j'ai écrite pour un "run all JUnits under a package". 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 pourriez voulez que cet extrait que bien (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);
    }
}
4
répondu Ran Biron 2009-09-15 19:51:50

Un fichier jar est juste un fichier zip avec un structurés manifeste. Vous pouvez ouvrir le fichier jar avec les outils habituels de Java zip et numériser le contenu du fichier de cette façon, gonfler les flux, etc. Alors utilise ça dans un appel getResourceAsStream, et ça devrait être tout joli dory.

modifier /après clarification

il m'a fallu une minute pour me souvenir de tous les morceaux et je suis sûr qu'il y a des moyens plus propres pour le faire, mais je voulais voir que je n'étais pas fou. Dans mon image de projet.jpg est un fichier d'une certaine partie du fichier jar. Je reçois le chargeur de classe de la classe principale (SomeClass est le point d'entrée) et l'utiliser pour découvrir l'image.jpg ressource. Puis un peu de magie de flux pour le faire entrer 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
répondu Mikeb 2009-09-15 21:01:21

avec un fichier JAR réel, vous pouvez lister le contenu en utilisant JarFile.entries() . Vous aurez besoin de connaître l'emplacement du fichier JAR cependant - vous ne pouvez pas simplement demander au classloader d'énumérer tout ce qu'il pourrait obtenir.

vous devriez être en mesure de trouver l'emplacement du fichier JAR basé sur L'URL retourné de ThisClassName.class.getResource("ThisClassName.class") , mais il peut être un peu fiddly.

3
répondu Jon Skeet 2011-04-15 11:57:22

il y a quelque temps j'ai fait une fonction qui obtient classess de L'intérieur 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
répondu berni 2011-05-27 23:28:40

voici un exemple d'utilisation de Reflections bibliothèque pour scanner récursivement classpath par le modèle de nom de regex augmenté d'un couple de Guava perks to fetch resources contents:

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 jarres et les classes explosées.

3
répondu Vadzim 2015-08-03 10:03:16
1
répondu jgran 2017-05-23 11:54:29

j'ai porté acheron55 la réponse de de Java 7 et fermé la FileSystem objet. Ce code fonctionne dans les IDE, dans les fichiers jar et dans un jar à l'intérieur d'une guerre sur Tomcat 7; mais notez qu'il ne pas travailler dans un jar à l'intérieur d'une guerre sur JBoss 7 (Il donne FileSystemNotFoundException: Provider "vfs" not installed , voir aussi ce post ). De plus, comme le code d'origine, il n'est pas sûr, comme suggéré par errr . Pour ces raisons, je abandonné cette solution; cependant, si vous pouvez accepter ces questions, voici mon code prêt à l'emploi:

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;
                }
            });
        }
    }
}
1
répondu Pino 2017-05-23 12:25:55

juste une façon différente de lister/lire des fichiers à partir D'une URL jar et il le fait récursivement pour les jarres imbriquées

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
répondu erolagnab 2015-06-17 03:00:21