Question

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 se terminant par ".txt". depuis le 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, afin que je puisse les charger avec:

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

(Celui-ci fonctionne car "CompanyLogo" est "codé en dur" mais le nombre d'images contenues dans le fichier JAR peut varier de 10 à 200 longueurs variables.)

MODIFIER

Je suppose donc que mon principal problème serait le suivant: comment connaître le nom du fichier JAR où réside ma classe principale?

Certes, je pourrais le lire avec 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 

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.

Était-ce utile?

La solution

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 sous 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. Cela faciliterait l'écriture de code qui gère les fichiers JAR et "éclaté". répertoires.

Autres conseils

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());
        }
    }
}

La answer de erickson 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() ));

À ceci:

String  [] webimages = ...

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

Je voudrais développer le répondre , car il s’agit d’une solution très peu sûre, pour plusieurs raisons:

  1. Cela 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’est pas vraiment nécessaire de synchroniser le nom de fichier; on pourrait simplement synchroniser sur le même objet à chaque fois (ou rendre la méthode synchronisée ), c'est purement une optimisation.

Je dirais que cela reste une solution problématique, car d’autres parties du code pourraient utiliser l’interface FileSystem sur les mêmes fichiers, ce qui risquerait de les interférer (même en cas de problème). application à thread unique).
De plus, il ne vérifie pas les null (par exemple, sur getClass (). GetResource () .

Cette interface Java NIO particulière est assez horrible, car elle introduit une ressource non sécurisée pour les threads globale / singleton, et sa documentation est extrêmement vague (beaucoup d’inconnues dues à des implémentations spécifiques au fournisseur). Les résultats peuvent varier pour d'autres fournisseurs FileSystem (pas JAR). Peut-être y a-t-il une bonne raison pour que ce soit ainsi; Je ne sais pas, je n'ai pas étudié les implémentations.

Voici une méthode que j'ai écrite pour "exécuter tous les JUnits sous un paquet". Vous devriez pouvoir 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));
            }
        }
    }
}

Modifier: 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);
    }
}
  
    

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

  

En supposant que votre projet soit emballé 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 fichier 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 fichier jar.

Un fichier jar est juste un fichier zip avec un manifeste structuré. Vous pouvez ouvrir le fichier jar avec les outils zip java habituels et analyser le contenu du fichier de cette façon, gonfler les flux, etc. Ensuite, utilisez-le dans un appel getResourceAsStream. Ce devrait être tout simplement.

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

À partir d'un fichier JAR réel, vous pouvez répertorier le contenu à l'aide de JarFile.entries () . Cependant, vous aurez besoin de connaître l’emplacement du fichier JAR. Vous ne pouvez pas simplement demander au chargeur de classe de tout répertorier.

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 peut s'agir d'un petit délicat.

Il y a quelque temps, j'ai créé une fonction qui obtient les classes de JAR à l'intérieur de la classe:

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;
}

Voici un exemple d'utilisation de la bibliothèque Reflections pour analyser récursivement classpath par modèle de nom regex complété par un couple. des guava avantages à récupérer le contenu des ressources:

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

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);
}

Ceci fonctionne à la fois avec les jars et les classes éclatées.

J'ai porté acheron55's répondez à Java 7 et fermez l'objet FileSystem . Ce code fonctionne dans les IDE, dans les fichiers jar et dans un jar dans une guerre contre Tomcat 7; Notez toutefois que cela ne ne fonctionne pas dans un fichier jar dans une guerre contre JBoss 7 (cela donne FileSystemNotFoundException: Le fournisseur "vfs" non installé , voir aussi cette publication ). En outre, comme le code d'origine, il n'est pas thread-safe, comme le suggère 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;
                }
            });
        }
    }
}

Juste une manière différente de lister / lire les fichiers à partir d'une URL de jar et le 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 
    }
});
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top