Obtenez une liste des ressources du répertoire classpath

247

Je cherche un moyen d'obtenir une liste de tous les noms de ressources à partir d'un répertoire classpath donné, quelque chose comme une méthode List<String> getResourceNames (String directoryName).

Par exemple, étant donné un répertoire classpath x/y/zfichiers contenant a.html, b.html, c.htmlet un sous - répertoire d, getResourceNames("x/y/z")doit retourner un List<String>contenant les chaînes suivantes: ['a.html', 'b.html', 'c.html', 'd'].

Il devrait fonctionner à la fois pour les ressources du système de fichiers et les fichiers jar.

Je sais que je peux écrire un extrait rapide avec Files, JarFiles et URLs, mais je ne veux pas réinventer la roue. Ma question est, étant donné les bibliothèques publiques existantes, quel est le moyen le plus rapide à mettre en œuvre getResourceNames? Les piles Spring et Apache Commons sont toutes deux réalisables.

viaclectique
la source
1
Réponse connexe: stackoverflow.com/a/30149061/4102160
Cfx
Vérifiez ce projet, résout l'analyse des dossiers de ressources: github.com/fraballi/resources-folder-scanner
Felix Aballi

Réponses:

165

Scanner personnalisé

Implémentez votre propre scanner. Par exemple:

private List<String> getResourceFiles(String path) throws IOException {
    List<String> filenames = new ArrayList<>();

    try (
            InputStream in = getResourceAsStream(path);
            BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
        String resource;

        while ((resource = br.readLine()) != null) {
            filenames.add(resource);
        }
    }

    return filenames;
}

private InputStream getResourceAsStream(String resource) {
    final InputStream in
            = getContextClassLoader().getResourceAsStream(resource);

    return in == null ? getClass().getResourceAsStream(resource) : in;
}

private ClassLoader getContextClassLoader() {
    return Thread.currentThread().getContextClassLoader();
}

Cadre Spring

Utiliser PathMatchingResourcePatternResolverdepuis Spring Framework.

Ronmamo Reflections

Les autres techniques peuvent être lentes à l'exécution pour les énormes valeurs CLASSPATH. Une solution plus rapide consiste à utiliser l' API Reflections de ronmamo , qui précompile la recherche au moment de la compilation.

iirekm
la source
12
si vous utilisez Reflections, tout ce dont vous avez réellement besoin:new Reflections("my.package", new ResourcesScanner()).getResources(pattern)
zapp
27
La première solution fonctionne-t-elle lorsqu'elle est exécutée à partir du fichier jar? Pour moi, ce n'est pas le cas. Je peux lire le fichier de cette façon, mais je ne peux pas lire le répertoire.
Valentina Chumak
5
La première méthode décrite dans cette réponse - lire le chemin en tant que ressource, ne semble pas fonctionner lorsque les ressources sont dans le même JAR que le code exécutable, au moins avec OpenJDK 1.8. L'échec est plutôt étrange - une NullPointerException est levée du plus profond de la logique de traitement des fichiers de la JVM. C'est comme si les concepteurs n'avaient pas vraiment anticipé cette utilisation des ressources, et il n'y a qu'une implémentation partielle. Même si cela fonctionne sur certains JDK ou environnements, cela ne semble pas être un comportement documenté.
Kevin Boone
7
Vous ne pouvez pas lire un répertoire comme celui-ci, car il n'y a pas de répertoire mais des flux de fichiers. Cette réponse est à moitié fausse.
Thomas Decaux
4
La première méthode ne semble pas fonctionner - du moins lors de l'exécution à partir de l'environnement de développement, avant de secouer les ressources. L'appel à getResourceAsStream()avec un chemin d'accès relatif à un répertoire conduit à un FileInputStream (File), qui est défini pour lever FileNotFoundException si le fichier est un répertoire.
Andy Thomas
52

Voici le code
Source : forums.devx.com/showthread.php?t=153784

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

/**
 * list resources available from the classpath @ *
 */
public class ResourceList{

    /**
     * for all elements of java.class.path get a Collection of resources Pattern
     * pattern = Pattern.compile(".*"); gets all resources
     * 
     * @param pattern
     *            the pattern to match
     * @return the resources in the order they are found
     */
    public static Collection<String> getResources(
        final Pattern pattern){
        final ArrayList<String> retval = new ArrayList<String>();
        final String classPath = System.getProperty("java.class.path", ".");
        final String[] classPathElements = classPath.split(System.getProperty("path.separator"));
        for(final String element : classPathElements){
            retval.addAll(getResources(element, pattern));
        }
        return retval;
    }

    private static Collection<String> getResources(
        final String element,
        final Pattern pattern){
        final ArrayList<String> retval = new ArrayList<String>();
        final File file = new File(element);
        if(file.isDirectory()){
            retval.addAll(getResourcesFromDirectory(file, pattern));
        } else{
            retval.addAll(getResourcesFromJarFile(file, pattern));
        }
        return retval;
    }

    private static Collection<String> getResourcesFromJarFile(
        final File file,
        final Pattern pattern){
        final ArrayList<String> retval = new ArrayList<String>();
        ZipFile zf;
        try{
            zf = new ZipFile(file);
        } catch(final ZipException e){
            throw new Error(e);
        } catch(final IOException e){
            throw new Error(e);
        }
        final Enumeration e = zf.entries();
        while(e.hasMoreElements()){
            final ZipEntry ze = (ZipEntry) e.nextElement();
            final String fileName = ze.getName();
            final boolean accept = pattern.matcher(fileName).matches();
            if(accept){
                retval.add(fileName);
            }
        }
        try{
            zf.close();
        } catch(final IOException e1){
            throw new Error(e1);
        }
        return retval;
    }

    private static Collection<String> getResourcesFromDirectory(
        final File directory,
        final Pattern pattern){
        final ArrayList<String> retval = new ArrayList<String>();
        final File[] fileList = directory.listFiles();
        for(final File file : fileList){
            if(file.isDirectory()){
                retval.addAll(getResourcesFromDirectory(file, pattern));
            } else{
                try{
                    final String fileName = file.getCanonicalPath();
                    final boolean accept = pattern.matcher(fileName).matches();
                    if(accept){
                        retval.add(fileName);
                    }
                } catch(final IOException e){
                    throw new Error(e);
                }
            }
        }
        return retval;
    }

    /**
     * list the resources that match args[0]
     * 
     * @param args
     *            args[0] is the pattern to match, or list all resources if
     *            there are no args
     */
    public static void main(final String[] args){
        Pattern pattern;
        if(args.length < 1){
            pattern = Pattern.compile(".*");
        } else{
            pattern = Pattern.compile(args[0]);
        }
        final Collection<String> list = ResourceList.getResources(pattern);
        for(final String name : list){
            System.out.println(name);
        }
    }
}  

Si vous utilisez Spring Jetez un œil à PathMatchingResourcePatternResolver

Jigar Joshi
la source
3
Questioner sait comment l'implémenter en utilisant des classes java standard, mais il veut savoir comment il peut utiliser les bibliothèques existantes (Spring, Apache Commons).
Jeroen Rosenberg
@Jeroen Rosenberg Il y a aussi une autre voie donnée qui a finalement été sélectionnée :)
Jigar Joshi
7
Je trouve cette solution utile pour les cas où vous n'utilisez pas Spring - il serait ridicule d'ajouter une dépendance à Spring uniquement à cause de cette fonctionnalité. Alors merci! "Sale" mais utile :) PS On pourrait vouloir utiliser File.pathSeparatorau lieu de codé :en dur dans la getResourcesméthode.
Timur
5
L'exemple de code dépend du système, utilisez-le à la place: final String[] classPathElements = classPath.split(System.pathSeparator);
décompilé le
1
Avertissement: la propriété système java.class.path peut ne pas contenir le chemin de classe réel sous lequel vous exécutez. Vous pouvez également regarder le chargeur de classe et l'interroger pour savoir à partir de quelles URL il charge les classes. L'autre mise en garde est bien sûr que si vous faites cela sous un SecurityManager, le constructeur ZipFile peut refuser votre accès au fichier, donc vous devriez le saisir.
Trejkaz
24

Utiliser des réflexions

Obtenez tout sur le chemin de classe:

Reflections reflections = new Reflections(null, new ResourcesScanner());
Set<String> resourceList = reflections.getResources(x -> true);

Un autre exemple - obtenez tous les fichiers avec l'extension .csv de some.package :

Reflections reflections = new Reflections("some.package", new ResourcesScanner());
Set<String> fileNames = reflections.getResources(Pattern.compile(".*\\.csv"));
NS du Toit
la source
existe-t-il un moyen d'y parvenir sans avoir à importer la dépendance google? Je voudrais éviter d'avoir cette dépendance juste pour effectuer cette tâche.
Enrico Giurin
1
Reflections n'est pas une bibliothèque Google.
Luke Hutchison
C'était peut-être dans le passé. Ma réponse est de 2015 et j'ai trouvé une référence à la bibliothèque en utilisant l'expression "Google Reflections" avant 2015. Je vois que le code a un peu changé et est passé de code.google.com ici à github ici
NS du Toit
@NSduToit qui était probablement un artefact de l'hébergement du code sur Google Code (pas une erreur rare), pas une revendication de paternité par Google.
matt b
17

Si vous utilisez apache commonsIO, vous pouvez utiliser pour le système de fichiers (en option avec le filtre d'extension):

Collection<File> files = FileUtils.listFiles(new File("directory/"), null, false);

et pour les ressources / classpath:

List<String> files = IOUtils.readLines(MyClass.class.getClassLoader().getResourceAsStream("directory/"), Charsets.UTF_8);

Si vous ne savez pas si "directoy /" est dans le système de fichiers ou dans les ressources, vous pouvez ajouter un

if (new File("directory/").isDirectory())

ou

if (MyClass.class.getClassLoader().getResource("directory/") != null)

avant les appels et utilisez les deux en combinaison ...

Rob
la source
23
Files! = Resources
djechlin
3
Bien sûr, les ressources ne sont pas toujours des fichiers mais la question portait sur les ressources provenant de fichiers / répertoires. Utilisez donc l'exemple de code si vous souhaitez vérifier un emplacement différent, par exemple si vous avez un config.xml dans vos ressources pour une configuration par défaut et qu'il devrait être possible de charger un config.xml externe à la place s'il existe ...
Rob
2
Et pourquoi les ressources ne sont pas des fichiers? Les ressources sont des fichiers archivés dans une archive zip. Ceux-ci sont chargés en mémoire et accessibles de manière spécifique mais je ne vois pas pourquoi ce ne sont pas des fichiers. Un exaple de ressource qui n'est pas «un fichier dans l'archive»?
Mike
10
Ne fonctionne pas (NullPointerException) lorsque la ressource cible est un répertoire qui réside dans un fichier JAR (c'est-à-dire que le JAR contient l'application principale et le répertoire cible)
Carlitos Way
12

Donc, en termes de PathMatchingResourcePatternResolver, c'est ce qui est nécessaire dans le code:

@Autowired
ResourcePatternResolver resourceResolver;

public void getResources() {
  resourceResolver.getResources("classpath:config/*.xml");
}
Pavel Kotlov
la source
juste un petit ajout si vous voulez rechercher toutes les ressources possibles dans tout le classpath*:config/*.xml
chemin de classe que
5

Les Spring framework« s PathMatchingResourcePatternResolverest vraiment génial pour ces choses:

private Resource[] getXMLResources() throws IOException
{
    ClassLoader classLoader = MethodHandles.lookup().getClass().getClassLoader();
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);

    return resolver.getResources("classpath:x/y/z/*.xml");
}

Dépendance Maven:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>LATEST</version>
</dependency>
BullyWiiPlaza
la source
5

Utilisé une combinaison de la réponse de Rob.

final String resourceDir = "resourceDirectory/";
List<String> files = IOUtils.readLines(Thread.currentThread().getClass().getClassLoader().getResourceAsStream(resourceDir), Charsets.UTF_8);

for(String f : files){
  String data= IOUtils.toString(Thread.currentThread().getClass().getClassLoader().getResourceAsStream(resourceDir + f));
  ....process data
}
Trevor
la source
comment obtenir toutes les ressources: c'est-à-dire en commençant par l'une /' or ou l' autre. »ou ./aucune d'entre elles n'a réellement fonctionné
javadba
3

Avec Spring, c'est facile. Qu'il s'agisse d'un fichier, d'un dossier ou même de plusieurs fichiers, il y a des chances que vous puissiez le faire par injection.

Cet exemple illustre l'injection de plusieurs fichiers situés dans le x/y/zdossier.

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

@Service
public class StackoverflowService {
    @Value("classpath:x/y/z/*")
    private Resource[] resources;

    public List<String> getResourceNames() {
        return Arrays.stream(resources)
                .map(Resource::getFilename)
                .collect(Collectors.toList());
    }
}

Il fonctionne pour les ressources dans le système de fichiers ainsi que dans les fichiers JAR.

naXa
la source
2
Je veux obtenir les noms des dossiers sous / BOOT-INF / classes / static / stories. J'essaie votre code avec @Value ("classpath: static / stories / *") et il retourne une liste vide lors de l'exécution du JAR. Cela fonctionne pour les ressources dans le chemin de classe, mais pas quand elles sont dans le JAR.
PS
@Value ("classpath: x / y / z / *") private Resource [] resources; m'a fait l'affaire. Ayez des heures de recherche pour ça! Merci!
Rudolf Schmidt
3

Cela devrait fonctionner (si le printemps n'est pas une option):

public static List<String> getFilenamesForDirnameFromCP(String directoryName) throws URISyntaxException, UnsupportedEncodingException, IOException {
    List<String> filenames = new ArrayList<>();

    URL url = Thread.currentThread().getContextClassLoader().getResource(directoryName);
    if (url != null) {
        if (url.getProtocol().equals("file")) {
            File file = Paths.get(url.toURI()).toFile();
            if (file != null) {
                File[] files = file.listFiles();
                if (files != null) {
                    for (File filename : files) {
                        filenames.add(filename.toString());
                    }
                }
            }
        } else if (url.getProtocol().equals("jar")) {
            String dirname = directoryName + "/";
            String path = url.getPath();
            String jarPath = path.substring(5, path.indexOf("!"));
            try (JarFile jar = new JarFile(URLDecoder.decode(jarPath, StandardCharsets.UTF_8.name()))) {
                Enumeration<JarEntry> entries = jar.entries();
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();
                    String name = entry.getName();
                    if (name.startsWith(dirname) && !dirname.equals(name)) {
                        URL resource = Thread.currentThread().getContextClassLoader().getResource(name);
                        filenames.add(resource.toString());
                    }
                }
            }
        }
    }
    return filenames;
}
fl0w
la source
Cela a reçu un downvote récemment - si vous avez trouvé un problème, merci de partager!
fl0w
1
Merci @ArnoUnkrig - veuillez partager votre solution mise à jour si vous le souhaitez
fl0w
3

À ma façon, pas de printemps, utilisé lors d'un test unitaire:

URI uri = TestClass.class.getResource("/resources").toURI();
Path myPath = Paths.get(uri);
Stream<Path> walk = Files.walk(myPath, 1);
for (Iterator<Path> it = walk.iterator(); it.hasNext(); ) {
    Path filename = it.next();   
    System.out.println(filename);
}
Jacques Koorts
la source
ne fonctionne pas lorsque vous exécutez un fichier jar: java.nio.file.FileSystemNotFoundException sur Paths.get (uri)
error1009
1

Le mécanisme le plus robuste pour répertorier toutes les ressources dans le chemin de classe est actuellement d'utiliser ce modèle avec ClassGraph , car il gère le plus large éventail possible de mécanismes de spécification de chemin de classe, y compris le nouveau système de modules JPMS. (Je suis l'auteur de ClassGraph.)

List<String> resourceNames;
try (ScanResult scanResult = new ClassGraph().whitelistPaths("x/y/z").scan()) {
    resourceNames = scanResult.getAllResources().getNames();
}
Luke Hutchison
la source
0

Je pense que vous pouvez utiliser le [ Fournisseur de système de fichiers Zip ] [1] pour y parvenir. Lors de son utilisation, FileSystems.newFileSystemil semble que vous puissiez traiter les objets de ce fichier ZIP comme un fichier "normal".

Dans la documentation liée ci-dessus:

Spécifiez les options de configuration du système de fichiers zip dans l'objet java.util.Map transmis à la FileSystems.newFileSystem méthode. Consultez la rubrique [Propriétés du système de fichiers zip] [2] pour plus d'informations sur les propriétés de configuration spécifiques au fournisseur pour le système de fichiers zip.

Une fois que vous disposez d'une instance d'un système de fichiers zip, vous pouvez appeler les méthodes des classes [ java.nio.file.FileSystem] [3] et [ java.nio.file.Path] [4] pour effectuer des opérations telles que la copie, le déplacement et le changement de nom des fichiers, ainsi que la modification des attributs de fichier.

La documentation du jdk.zipfsmodule dans [États Java 11] [5]:

Le fournisseur de système de fichiers zip traite un fichier zip ou JAR comme un système de fichiers et offre la possibilité de manipuler le contenu du fichier. Le fournisseur du système de fichiers zip peut être créé par [ FileSystems.newFileSystem] [6] s'il est installé.

Voici un exemple artificiel que j'ai fait en utilisant vos ressources d'exemple. Notez que a .zipest un .jar, mais vous pouvez adapter votre code pour utiliser à la place des ressources de chemin de classe:

Installer

cd /tmp
mkdir -p x/y/z
touch x/y/z/{a,b,c}.html
echo 'hello world' > x/y/z/d
zip -r example.zip x

Java

import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.util.Collections;
import java.util.stream.Collectors;

public class MkobitZipRead {

  public static void main(String[] args) throws IOException {
    final URI uri = URI.create("jar:file:/tmp/example.zip");
    try (
        final FileSystem zipfs = FileSystems.newFileSystem(uri, Collections.emptyMap());
    ) {
      Files.walk(zipfs.getPath("/")).forEach(path -> System.out.println("Files in zip:" + path));
      System.out.println("-----");
      final String manifest = Files.readAllLines(
          zipfs.getPath("x", "y", "z").resolve("d")
      ).stream().collect(Collectors.joining(System.lineSeparator()));
      System.out.println(manifest);
    }
  }

}

Production

Files in zip:/
Files in zip:/x/
Files in zip:/x/y/
Files in zip:/x/y/z/
Files in zip:/x/y/z/c.html
Files in zip:/x/y/z/b.html
Files in zip:/x/y/z/a.html
Files in zip:/x/y/z/d
-----
hello world
mkobit
la source
0

Aucune des réponses n'a fonctionné pour moi, même si mes ressources ont été placées dans des dossiers de ressources et que j'ai suivi les réponses ci-dessus. Ce qui a fait un tour était:

@Value("file:*/**/resources/**/schema/*.json")
private Resource[] resources;
kukis
la source
-5

Sur la base des informations de @rob ci-dessus, j'ai créé l'implémentation que je publie dans le domaine public:

private static List<String> getClasspathEntriesByPath(String path) throws IOException {
    InputStream is = Main.class.getClassLoader().getResourceAsStream(path);

    StringBuilder sb = new StringBuilder();
    while (is.available()>0) {
        byte[] buffer = new byte[1024];
        sb.append(new String(buffer, Charset.defaultCharset()));
    }

    return Arrays
            .asList(sb.toString().split("\n"))          // Convert StringBuilder to individual lines
            .stream()                                   // Stream the list
            .filter(line -> line.trim().length()>0)     // Filter out empty lines
            .collect(Collectors.toList());              // Collect remaining lines into a List again
}

Bien que je ne m'attendais pas getResourcesAsStreamà travailler comme ça sur un répertoire, ça marche vraiment et ça marche bien.

Deven Phillips
la source