Ottieni un elenco di risorse dalla directory classpath


247

Sto cercando un modo per ottenere un elenco di tutti i nomi delle risorse da una determinata directory classpath, qualcosa come un metodo List<String> getResourceNames (String directoryName).

Ad esempio, data una directory percorso di classe x/y/zche contiene i file a.html, b.html, c.htmle una sottodirectory d, getResourceNames("x/y/z")dovrebbe restituire una List<String>contenente le seguenti stringhe: ['a.html', 'b.html', 'c.html', 'd'].

Dovrebbe funzionare sia per le risorse nel filesystem che nei vasetti.

So che posso scrivere un rapido frammento con Files, JarFiles e URLs, ma io non voglio reinventare la ruota. La mia domanda è, date le librerie disponibili pubblicamente esistenti, qual è il modo più rapido per implementare getResourceNames? Le pile di Spring e Apache Commons sono entrambe possibili.



Controlla questo progetto, risolve la scansione della cartella delle risorse: github.com/fraballi/resources-folder-scanner
Felix Aballi

Risposte:


165

Scanner personalizzato

Implementa il tuo scanner. Per esempio:

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

Quadro di primavera

Uso PathMatchingResourcePatternResolver da Spring Framework.

Ronmamo Reflections

Le altre tecniche potrebbero essere lente in fase di esecuzione per enormi valori di CLASSPATH. Una soluzione più veloce consiste nell'utilizzare l' API Reflections di ronmamo , che precompila la ricerca in fase di compilazione.


12
se usi Reflections, tutto ciò di cui hai effettivamente bisogno:new Reflections("my.package", new ResourcesScanner()).getResources(pattern)
zapp

27
La prima soluzione funziona quando viene eseguita dal file jar? Per me - non lo è. Posso leggere il file in questo modo, ma non riesco a leggere la directory.
Valentina Chumak,

5
Il primo metodo descritto in questa risposta - leggere il percorso come risorsa, non sembra funzionare quando le risorse si trovano nello stesso JAR del codice eseguibile, almeno con OpenJDK 1.8. L'errore è piuttosto strano: una NullPointerException viene lanciata dal profondo nella logica di elaborazione dei file della JVM. È come se i progettisti non avessero davvero previsto questo uso delle risorse e ci fosse solo un'implementazione parziale. Anche se funziona su alcuni JDK o ambienti, questo non sembra essere un comportamento documentato.
Kevin Boone,

7
Non puoi leggere la directory in questo modo, poiché non esiste alcuna directory ma flussi di file. Questa risposta è per metà sbagliata.
Thomas Decaux,

4
Il primo metodo non sembra funzionare, almeno quando si esegue dall'ambiente di sviluppo, prima di ridurre le risorse. La chiamata a getResourceAsStream()con un percorso relativo a una directory porta a FileInputStream (File), che è definito per generare FileNotFoundException se il file è una directory.
Andy Thomas,

52

Ecco il codice
Fonte : forum.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);
        }
    }
}  

Se stai usando Spring Dai un'occhiata a PathMatchingResourcePatternResolver


3
Interrogatore sa come implementarlo usando le classi java standard, ma vuole sapere come utilizzare le librerie esistenti (Spring, Apache Commons).
Jeroen Rosenberg,

@Jeroen Rosenberg C'è anche un altro modo dato che è stato finalmente selezionato :)
Jigar Joshi

7
Trovo questa soluzione utile per i casi in cui non usi Spring - sarebbe ridicolo aggiungere una dipendenza da Spring solo per questa funzione. Quindi grazie! "Dirty" ma utile :) PS One potrebbe desiderare di utilizzare File.pathSeparatoral posto di hard-coded :nel getResourcesmetodo.
Timur,

5
L'esempio di codice dipende dal sistema, utilizzare invece questo: final String[] classPathElements = classPath.split(System.pathSeparator);
compilato il

1
Avvertenza: la proprietà di sistema java.class.path potrebbe non contenere il percorso di classe effettivo in esecuzione. In alternativa, puoi guardare il caricatore di classi e interrogarlo per quali URL sta caricando le classi. L'altro avvertimento ovviamente è che se lo stai facendo sotto un SecurityManager, il costruttore ZipFile può rifiutare l'accesso al file, quindi dovresti prenderlo.
Trejkaz,

24

Usando le riflessioni

Ottieni tutto sul percorso di classe:

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

Un altro esempio: ottenere tutti i file con estensione .csv da some.package :

Reflections reflections = new Reflections("some.package", new ResourcesScanner());
Set<String> fileNames = reflections.getResources(Pattern.compile(".*\\.csv"));

c'è un modo per ottenere lo stesso senza dover importare la dipendenza google? Vorrei evitare di avere questa dipendenza solo per svolgere questo compito.
Enrico Giurin,

1
Reflections non è una libreria di Google.
Luke Hutchison,

Forse era in passato. La mia risposta è del 2015 e ho trovato qualche riferimento alla biblioteca usando la frase "Google Reflections" prima del 2015. Vedo che il codice si è spostato un po 'e si è spostato da code.google.com qui a github qui
NS du Toit

@NSduToit che probabilmente era un artefatto di ospitare il codice su Google Code (non un errore non comune), non una rivendicazione di paternità da parte di Google.
matt b

17

Se usi apache commonsIO puoi usarlo per il filesystem (opzionalmente con filtro di estensione):

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

e per risorse / percorso di classe:

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

Se non sai se "directoy /" si trova nel filesystem o nelle risorse puoi aggiungere a

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

o

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

prima delle chiamate e utilizzare entrambi in combinazione ...


23
Files! = Resources
djechlin

3
Certo, le risorse potrebbero non essere sempre file, ma la domanda riguardava le risorse provenienti da file / directory. Quindi utilizzare il codice di esempio se si desidera controllare una posizione diversa, ad esempio se si dispone di un file config.xml per una configurazione predefinita e dovrebbe essere possibile caricare un file config.xml esterno se esiste ...
Rob

2
E perché le risorse non sono file? Le risorse sono file archiviati nell'archivio zip. Questi vengono caricati in memoria e accessibili in modo specifico, ma non riesco a capire perché non siano file. Qualche esempio di risorsa che non sia "un file all'interno dell'archivio"?
Mike,

10
Non funziona (NullPointerException) quando la risorsa di destinazione è una directory che risiede all'interno di un file JAR (ovvero il JAR contiene l'app principale e la directory di destinazione)
Carlitos Way,

12

Quindi in termini di PathMatchingResourcePatternResolver questo è ciò che è necessario nel codice:

@Autowired
ResourcePatternResolver resourceResolver;

public void getResources() {
  resourceResolver.getResources("classpath:config/*.xml");
}

solo una piccola aggiunta se vuoi cercare tutte le possibili risorse in tutti i percorsi di classe che devi usareclasspath*:config/*.xml
revau.lt

5

I Spring framework's PathMatchingResourcePatternResolverè davvero impressionante per queste cose:

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

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

Dipendenza da Maven:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>LATEST</version>
</dependency>

5

Ho usato una combinazione della risposta di 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
}

come ottenere tutte le risorse: cioè iniziare con uno dei due /' or .` o ./nessuno dei quali ha effettivamente funzionato
javadba

3

Con Spring è facile. Che si tratti di un file, o cartella, o anche più file, ci sono possibilità, puoi farlo tramite iniezione.

Questo esempio dimostra l'iniezione di più file situati nella x/y/zcartella.

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

Funziona con risorse nel filesystem e nelle JAR.


2
Voglio ottenere i nomi delle cartelle in / BOOT-INF / classes / static / stories. Provo il tuo codice con @Value ("classpath: static / stories / *") e restituisce un elenco vuoto quando eseguo il JAR. Funziona per le risorse nel percorso di classe, ma non quando si trovano nel JAR.
PS

@Value ("classpath: x / y / z / *") risorse private []; mi ha fatto il trucco. Hai ore di ricerca per questo! Grazie!
Rudolf Schmidt,

3

Questo dovrebbe funzionare (se la primavera non è un'opzione):

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

Questo ha ricevuto un downvote di recente: se hai riscontrato un problema, ti preghiamo di condividere, grazie!
fl0w,

1
Grazie @ArnoUnkrig - per favore condividi la tua soluzione aggiornata se vuoi
fl0w

3

A modo mio, senza molla, usato durante un test unitario:

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

non funziona quando si esegue un jar: java.nio.file.FileSystemNotFoundException at Paths.get (uri)
errore1009,

1

Il meccanismo più affidabile per elencare tutte le risorse nel percorso di classe è attualmente utilizzare questo modello con ClassGraph , poiché gestisce la più ampia gamma possibile di meccanismi di specifica del percorso di classe , incluso il nuovo sistema di moduli JPMS. (Sono l'autore di ClassGraph.)

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

0

Penso che tu possa sfruttare il [ Zip File System Provider ] [1] per raggiungere questo obiettivo. Quando lo usi FileSystems.newFileSystemsembra che tu possa trattare gli oggetti in quel ZIP come un file "normale".

Nella documentazione collegata sopra:

Specificare le opzioni di configurazione per il file system zip nell'oggetto java.util.Map passato a FileSystems.newFileSystem metodo. Consultare l'argomento [Proprietà del file system zip] [2] per informazioni sulle proprietà di configurazione specifiche del provider per il file system zip.

Una volta che hai un'istanza di un file system zip, puoi invocare i metodi delle classi [ java.nio.file.FileSystem] [3] e [ java.nio.file.Path] [4] per eseguire operazioni come la copia, lo spostamento e la ridenominazione dei file, nonché la modifica degli attributi dei file.

La documentazione per il jdk.zipfsmodulo in [stati Java 11] [5]:

Il provider del file system zip considera un file zip o JAR come un file system e offre la possibilità di manipolare il contenuto del file. Il provider del file system zip può essere creato da [ FileSystems.newFileSystem] [6] se installato.

Ecco un esempio inventato che ho fatto usando le tue risorse di esempio. Si noti che a .zipè a .jar, ma è possibile adattare il codice per utilizzare invece le risorse del percorso di classe:

Impostare

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

Giava

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

}

Produzione

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

0

Nessuna delle risposte ha funzionato per me, anche se le mie risorse sono state inserite nelle cartelle delle risorse e ho seguito le risposte precedenti. Ciò che ha fatto un trucco è stato:

@Value("file:*/**/resources/**/schema/*.json")
private Resource[] resources;

-5

Sulla base delle informazioni di @rob sopra riportate, ho creato l'implementazione che sto rilasciando di dominio pubblico:

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
}

Anche se non mi sarei aspettato getResourcesAsStreamdi funzionare così su una directory, funziona davvero e funziona bene.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.