Ruft eine Liste der Ressourcen aus dem Klassenpfadverzeichnis ab

247

Ich suche nach einer Möglichkeit, eine Liste aller Ressourcennamen aus einem bestimmten Klassenpfadverzeichnis abzurufen, ähnlich einer Methode List<String> getResourceNames (String directoryName).

Um zum Beispiel ein Classpath - Verzeichnis angegeben x/y/zenthält Dateien a.html, b.html, c.htmlund ein Unterverzeichnis d, getResourceNames("x/y/z")sollte zurückgeben eine List<String>der folgenden Zeichenketten enthalten: ['a.html', 'b.html', 'c.html', 'd'].

Es sollte sowohl für Ressourcen im Dateisystem als auch für Jars funktionieren.

Ich weiß, dass ich mit Files, JarFiles und URLs einen kurzen Ausschnitt schreiben kann , aber ich möchte das Rad nicht neu erfinden. Meine Frage ist angesichts der vorhandenen öffentlich verfügbaren Bibliotheken, wie diese am schnellsten implementiert werden kann getResourceNames. Spring- und Apache Commons-Stapel sind beide machbar.

viaclectic
quelle
1
Verwandte Antwort: stackoverflow.com/a/30149061/4102160
Cfx
Überprüfen Sie dieses Projekt und lösen Sie das Scannen von Ressourcenordnern: github.com/fraballi/resources-folder-scanner
Felix Aballi

Antworten:

165

Benutzerdefinierter Scanner

Implementieren Sie Ihren eigenen Scanner. Beispielsweise:

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

Frühlingsrahmen

Verwendung PathMatchingResourcePatternResolveraus Spring Framework.

Ronmamo Reflexionen

Die anderen Techniken können zur Laufzeit für große CLASSPATH-Werte langsam sein. Eine schnellere Lösung ist die Verwendung der Reflections-API von ronmamo , die die Suche zur Kompilierungszeit vorkompiliert.

iirekm
quelle
12
Wenn Sie Reflections verwenden, brauchen Sie eigentlich nur:new Reflections("my.package", new ResourcesScanner()).getResources(pattern)
Zapp
27
Funktioniert die erste Lösung, wenn sie aus einer JAR-Datei ausgeführt wird? Für mich - das tut es nicht. Ich kann Dateien auf diese Weise lesen, aber kein Verzeichnis lesen.
Valentina Chumak
5
Die erste in dieser Antwort beschriebene Methode - das Lesen des Pfads als Ressource - scheint nicht zu funktionieren, wenn sich die Ressourcen in derselben JAR wie der ausführbare Code befinden, zumindest mit OpenJDK 1.8. Der Fehler ist ziemlich seltsam - eine NullPointerException wird tief in der Dateiverarbeitungslogik der JVM ausgelöst. Es ist, als hätten die Designer diesen Ressourceneinsatz nicht wirklich erwartet, und es gibt nur eine teilweise Implementierung. Selbst wenn es in einigen JDKs oder Umgebungen funktioniert, scheint dies kein dokumentiertes Verhalten zu sein.
Kevin Boone
7
Sie können ein solches Verzeichnis nicht lesen, da es kein Verzeichnis außer Dateistreams gibt. Diese Antwort ist halb falsch.
Thomas Decaux
4
Die erste Methode scheint nicht zu funktionieren - zumindest wenn sie in der Entwicklungsumgebung ausgeführt wird, bevor die Ressourcen aufgebraucht werden. Der Aufruf von getResourceAsStream()mit einem relativen Pfad zu einem Verzeichnis führt zu einem FileInputStream (File), der definiert ist, um FileNotFoundException auszulösen, wenn die Datei ein Verzeichnis ist.
Andy Thomas
52

Hier ist der Code
Quelle : 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);
        }
    }
}  

Wenn Sie Spring verwenden, schauen Sie sich PathMatchingResourcePatternResolver an

Jigar Joshi
quelle
3
Der Fragesteller weiß, wie man es mit Standard-Java-Klassen implementiert, aber er möchte wissen, wie er vorhandene Bibliotheken (Spring, Apache Commons) nutzen kann.
Jeroen Rosenberg
@ Jeroen Rosenberg Es gibt auch einen anderen Weg, der endgültig ausgewählt wurde :)
Jigar Joshi
7
Ich finde diese Lösung nützlich für Fälle, in denen Sie Spring nicht verwenden. Es wäre lächerlich, nur aufgrund dieser Funktion eine Abhängigkeit von Spring hinzuzufügen. Also vielen Dank! „Dirty“ , aber nützlich :) PS Man könnte verwendet werden soll , File.pathSeparatoranstatt hartcodiert :in getResourcesVerfahren.
Timur
5
Das Codebeispiel ist systemabhängig. Verwenden Sie stattdessen final String[] classPathElements = classPath.split(System.pathSeparator);
Folgendes
1
Vorsichtsmaßnahme: Die Systemeigenschaft java.class.path enthält möglicherweise nicht den tatsächlichen Klassenpfad, unter dem Sie ausgeführt werden. Alternativ können Sie sich den Klassenlader ansehen und ihn nach den URLs abfragen, von denen er Klassen lädt. Die andere Einschränkung ist natürlich, dass der ZipFile-Konstruktor Ihren Zugriff auf die Datei ablehnen kann, wenn Sie dies unter einem SecurityManager tun. Sie sollten dies also abfangen.
Trejkaz
24

Mit Reflexionen

Holen Sie sich alles auf den Klassenpfad:

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

Ein weiteres Beispiel : Holen Sie sich alle Dateien mit der Erweiterung .csv aus some.package :

Reflections reflections = new Reflections("some.package", new ResourcesScanner());
Set<String> fileNames = reflections.getResources(Pattern.compile(".*\\.csv"));
NS du Toit
quelle
Gibt es eine Möglichkeit, dasselbe zu erreichen, ohne die Google-Abhängigkeit importieren zu müssen? Ich möchte diese Abhängigkeit vermeiden, nur um diese Aufgabe auszuführen.
Enrico Giurin
1
Reflections ist keine Google-Bibliothek.
Luke Hutchison
Vielleicht war es in der Vergangenheit. Meine Antwort stammt aus dem Jahr 2015 und ich habe vor 2015 einen Verweis auf die Bibliothek mit dem Ausdruck "Google Reflections" gefunden. Ich sehe, dass sich der Code ein wenig verschoben hat und von code.google.com hier zu github hier
NS du Toit
@NSduToit, das wahrscheinlich ein Artefakt beim Hosten des Codes auf Google Code war (kein ungewöhnlicher Fehler), kein Anspruch auf Urheberschaft von Google.
Matt b
17

Wenn Sie Apache commonsIO verwenden, können Sie für das Dateisystem verwenden (optional mit Erweiterungsfilter):

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

und für Ressourcen / Klassenpfad:

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

Wenn Sie nicht wissen, ob sich "directoy /" im Dateisystem oder in Ressourcen befindet, können Sie a hinzufügen

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

oder

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

vor den Anrufen und verwenden Sie beide in Kombination ...

rauben
quelle
23
Dateien! = Ressourcen
Djechlin
3
Sicher, Ressourcen sind möglicherweise nicht immer Dateien, aber die Frage betraf Ressourcen, die aus Dateien / Verzeichnissen stammen. Verwenden Sie also den Beispielcode, wenn Sie einen anderen Speicherort überprüfen möchten, z. B. wenn Ihre Ressourcen eine config.xml für eine Standardkonfiguration enthalten und es möglich sein sollte, stattdessen eine externe config.xml zu laden, falls vorhanden ...
Rob
2
Und warum sind Ressourcen keine Dateien? Ressourcen sind Dateien, die im Zip-Archiv archiviert werden. Diese werden in den Speicher geladen und auf bestimmte Weise aufgerufen, aber ich kann nicht verstehen, warum es sich nicht um Dateien handelt. Gibt es ein Beispiel für eine Ressource, die keine 'Datei im Archiv' ist?
Mike
10
Funktioniert nicht (NullPointerException), wenn die Zielressource ein Verzeichnis ist, das sich in einer JAR-Datei befindet (dh die JAR enthält die Haupt-App und das Zielverzeichnis)
Carlitos Way
12

In Bezug auf den PathMatchingResourcePatternResolver wird im Code Folgendes benötigt:

@Autowired
ResourcePatternResolver resourceResolver;

public void getResources() {
  resourceResolver.getResources("classpath:config/*.xml");
}
Pavel Kotlov
quelle
Nur eine kleine Ergänzung, wenn Sie alle möglichen Ressourcen in allen Klassenpfaden durchsuchen möchten, die Sie verwenden müssenclasspath*:config/*.xml
revau.lt
5

Die Spring framework‚s PathMatchingResourcePatternResolverist wirklich genial für diese Dinge:

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

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

Maven-Abhängigkeit:

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

Verwendete eine Kombination aus Robs Antwort.

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
quelle
wie man alle Ressourcen bekommt : dh mit entweder beginnen /' or .` oder ./keine davon hat tatsächlich funktioniert
javadba
3

Mit Spring ist es einfach. Sei es eine Datei oder ein Ordner oder sogar mehrere Dateien, es besteht die Möglichkeit, dass Sie dies per Injektion tun können.

Dieses Beispiel zeigt das Einfügen mehrerer Dateien im x/y/zOrdner.

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

Es funktioniert sowohl für Ressourcen im Dateisystem als auch in JARs.

naXa
quelle
2
Ich möchte die Ordnernamen unter / BOOT-INF / classes / static / stories abrufen. Ich versuche Ihren Code mit @Value ("Klassenpfad: statisch / Geschichten / *") und es gibt eine leere Liste zurück, wenn die JAR ausgeführt wird. Es funktioniert für Ressourcen im Klassenpfad, jedoch nicht, wenn sie sich in der JAR befinden.
PS
@Value ("Klassenpfad: x / y / z / *") private Resource [] resources; hat mir den Trick getan. Habe Suchstunden dafür! Vielen Dank!
Rudolf Schmidt
3

Dies sollte funktionieren (wenn der Frühling keine Option ist):

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
quelle
Dies wurde kürzlich abgelehnt - wenn Sie ein Problem damit gefunden haben, teilen Sie es bitte mit, danke!
fl0w
1
Vielen Dank @ArnoUnkrig - bitte teilen Sie Ihre aktualisierte Lösung, wenn Sie
möchten
3

Mein Weg, keine Feder, während eines Unit-Tests verwendet:

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
quelle
funktioniert nicht, wenn Sie ein jar ausführen: java.nio.file.FileSystemNotFoundException at Paths.get (uri)
error1009
0

Ich denke, Sie können den [ Zip File System Provider ] [1] nutzen, um dies zu erreichen. Wenn FileSystems.newFileSystemSie es verwenden, können Sie die Objekte in dieser ZIP-Datei als "normale" Datei behandeln.

In der oben verlinkten Dokumentation:

Geben Sie die Konfigurationsoptionen für das Zip-Dateisystem im Objekt java.util.Map an, das an übergeben wird FileSystems.newFileSystem Methode übergeben wird. Informationen zu den anbieterspezifischen Konfigurationseigenschaften für das Zip-Dateisystem finden Sie im Thema [Eigenschaften des Zip-Dateisystems] [2].

Sobald Sie eine Instanz eines Zip-Dateisystems haben, können Sie die Methoden der Klassen [ java.nio.file.FileSystem] [3] und [ java.nio.file.Path] [4] aufrufen , um Vorgänge wie das Kopieren, Verschieben und Umbenennen von Dateien sowie das Ändern von Dateiattributen auszuführen.

Die Dokumentation für das jdk.zipfsModul in [Java 11-Zustände] [5]:

Der Zip-Dateisystemanbieter behandelt eine Zip- oder JAR-Datei als Dateisystem und bietet die Möglichkeit, den Inhalt der Datei zu bearbeiten. Der Zip-Dateisystemanbieter kann FileSystems.newFileSystembei Installation von [ ] [6] erstellt werden.

Hier ist ein erfundenes Beispiel, das ich mit Ihren Beispielressourcen erstellt habe. Beachten Sie, dass a a .zipist.jar , aber Sie können Ihren Code anpassen, um stattdessen Klassenpfadressourcen zu verwenden:

Konfiguration

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

}

Ausgabe

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
quelle
0

Keine der Antworten funktionierte für mich, obwohl ich meine Ressourcen in Ressourcenordnern abgelegt hatte und den obigen Antworten folgte. Was einen Trick machte, war:

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

Basierend auf den obigen Informationen von @rob habe ich die Implementierung erstellt, die ich öffentlich zugänglich mache:

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
}

Ich hätte zwar nicht erwartet getResourcesAsStream, dass es in einem Verzeichnis so funktioniert, aber es funktioniert wirklich und es funktioniert gut.

Deven Phillips
quelle