Uzyskaj listę zasobów z katalogu ścieżki klasy

247

Szukam sposobu na uzyskanie listy wszystkich nazw zasobów z danego katalogu ścieżki klas, coś w rodzaju metody List<String> getResourceNames (String directoryName).

Na przykład, biorąc pod uwagę katalog ścieżce klasy x/y/zzawierający pliki a.html, b.html, c.htmloraz podkatalog d, getResourceNames("x/y/z")powinien zwrócić List<String>zawierające następujące ciągi: ['a.html', 'b.html', 'c.html', 'd'].

Powinien działać zarówno dla zasobów w systemie plików, jak i słoikach.

Wiem, że mogę napisać krótki urywek za pomocą Files, JarFiles i URLs, ale nie chcę wymyślać koła ponownie. Moje pytanie brzmi: biorąc pod uwagę istniejące publicznie dostępne biblioteki, jaki jest najszybszy sposób wdrożenia getResourceNames? Możliwe są stosy wiosenne i Apache Commons.

viaclectic
źródło
1
Powiązana odpowiedź: stackoverflow.com/a/30149061/4102160
Cfx
Sprawdź ten projekt, rozwiązuje skanowanie folderów zasobów: github.com/fraballi/resources-folder-scanner
Felix Aballi

Odpowiedzi:

165

Niestandardowy skaner

Zaimplementuj własny skaner. Na przykład:

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

Spring Framework

Użyj PathMatchingResourcePatternResolverz Spring Framework.

Ronmamo Refleksje

Inne techniki mogą być powolne w czasie wykonywania dla ogromnych wartości CLASSPATH. Szybszym rozwiązaniem jest użycie interfejsu API Reflections ronmamo , który wstępnie kompiluje wyszukiwanie w czasie kompilacji.

iirekm
źródło
12
jeśli używasz Refleksji, wszystko, czego potrzebujesz:new Reflections("my.package", new ResourcesScanner()).getResources(pattern)
zapp
27
Czy pierwsze rozwiązanie działa po uruchomieniu z pliku jar? Dla mnie nie. W ten sposób mogę odczytać plik, ale nie mogę odczytać katalogu.
Valentina Chumak
5
Pierwsza metoda opisana w tej odpowiedzi - odczytywanie ścieżki jako zasobu, wydaje się nie działać, gdy zasoby znajdują się w tym samym pliku JAR co kod wykonywalny, przynajmniej w OpenJDK 1.8. Niepowodzenie jest raczej dziwne - z głębi logiki przetwarzania plików JVM generowany jest wyjątek NullPointerException. To tak, jakby projektanci tak naprawdę nie przewidzieli takiego wykorzystania zasobów, a tylko częściowe wdrożenie. Nawet jeśli działa w niektórych JDK lub środowiskach, nie wydaje się to udokumentowanym zachowaniem.
Kevin Boone
7
Nie można odczytać katalogu w ten sposób, ponieważ nie ma katalogu poza strumieniami plików. Ta odpowiedź jest w połowie błędna.
Thomas Decaux,
4
Pierwsza metoda wydaje się nie działać - przynajmniej podczas uruchamiania ze środowiska programistycznego, przed zebraniem zasobów. Wywołanie getResourceAsStream()z względną ścieżką do katalogu prowadzi do FileInputStream (File), który jest zdefiniowany w celu zgłaszania wyjątku FileNotFoundException, jeśli plik jest katalogiem.
Andy Thomas
52

Oto kod
Źródło : 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);
        }
    }
}  

Jeśli używasz Spring Spójrz na PathMatchingResourcePatternResolver

Jigar Joshi
źródło
3
Pytający wie, jak zaimplementować go za pomocą standardowych klas java, ale chce wiedzieć, jak wykorzystać istniejące biblioteki (Spring, Apache Commons).
Jeroen Rosenberg
@Jeroen Rosenberg Istnieje również inny sposób, który został ostatecznie wybrany :)
Jigar Joshi
7
Uważam to rozwiązanie za przydatne w przypadkach, w których nie używasz Springa - niedorzeczne byłoby dodanie zależności od wiosny tylko z powodu tej funkcji. Więc dziękuję! „Brudne”, ale przydatne :) PS Można chcieć użyć File.pathSeparatorzamiast metody :na stałe zakodowanej getResources.
Timur
5
Przykład kodu zależy od systemu, użyj tego zamiast tego: final String[] classPathElements = classPath.split(System.pathSeparator);
skompilowany
1
Uwaga: Właściwość systemowa java.class.path może nie zawierać faktycznej ścieżki klas, na której się uruchamiasz. Możesz również spojrzeć na moduł ładujący klasy i przesłuchać go pod kątem adresów URL, z których ładuje klasy. Drugim zastrzeżeniem jest oczywiście to, że jeśli robisz to w SecurityManager, konstruktor ZipFile może odrzucić twój dostęp do pliku, więc powinieneś to złapać.
Trejkaz
24

Korzystanie z odbić

Uzyskaj wszystko na ścieżce klas:

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

Kolejny przykład - pobierz wszystkie pliki z rozszerzeniem .csv z some.package :

Reflections reflections = new Reflections("some.package", new ResourcesScanner());
Set<String> fileNames = reflections.getResources(Pattern.compile(".*\\.csv"));
NS du Toit
źródło
czy jest jakiś sposób na osiągnięcie tego samego bez konieczności importowania zależności Google? Chciałbym uniknąć tej zależności tylko po to, aby wykonać to zadanie.
Enrico Giurin
1
Refleksje nie jest biblioteką Google.
Luke Hutchison
Może to było w przeszłości. Moja odpowiedź pochodzi z 2015 r. I do 2015 r. Odnalazłem pewne odniesienia do biblioteki, używając frazy „Google Reflections”. Widzę, że kod trochę się zmienił i przeniosłem z code.google.com tutaj na github tutaj
NS du Toit
@NSduToit był to prawdopodobnie artefakt hostowania kodu w Google Code (niezbyt częsty błąd), a nie roszczenie autorstwa Google.
matt b
17

Jeśli używasz apache commonsIO, możesz użyć dla systemu plików (opcjonalnie z filtrem rozszerzeń):

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

a dla zasobów / ścieżki klasy:

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

Jeśli nie wiesz, czy „directoy /” znajduje się w systemie plików lub zasobach, możesz dodać plik

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

lub

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

przed połączeniami i używaj obu w połączeniu ...

Obrabować
źródło
23
Pliki! = Zasoby
djechlin
3
Oczywiście zasoby nie zawsze mogą być plikami, ale pytanie dotyczyło zasobów pochodzących z plików / katalogów. Użyj więc przykładowego kodu, jeśli chcesz sprawdzić inną lokalizację, np. Jeśli masz plik config.xml w swoich zasobach dla pewnej domyślnej konfiguracji i zamiast tego powinno być możliwe załadowanie zewnętrznego pliku config.xml, jeśli istnieje ...
Rob
2
A dlaczego zasoby nie są plikami? Zasoby to pliki zarchiwizowane w archiwum zip. Są one ładowane do pamięci i dostępne w określony sposób, ale nie rozumiem, dlaczego nie są plikami. Każdy przykład zasobu, który nie jest „plikiem w archiwum”?
Mike
10
Nie działa (wyjątek NullPointerException), gdy zasób docelowy jest katalogiem znajdującym się w pliku JAR (tzn. JAR zawiera aplikację główną i katalog docelowy)
Carlitos Way
12

Jeśli chodzi o PathMatchingResourcePatternResolver, to jest to, czego potrzeba w kodzie:

@Autowired
ResourcePatternResolver resourceResolver;

public void getResources() {
  resourceResolver.getResources("classpath:config/*.xml");
}
Pavel Kotlov
źródło
tylko mały dodatek, jeśli chcesz przeszukać wszystkie możliwe zasoby we wszystkich ścieżkach klasy, których potrzebujeszclasspath*:config/*.xml
revau.lt
5

W Spring framework„s PathMatchingResourcePatternResolverjest naprawdę niesamowite dla tych rzeczy:

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

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

Zależność od Maven:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>LATEST</version>
</dependency>
BullyWiiPlaza
źródło
5

Użyłem kombinacji odpowiedzi Roba.

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
źródło
jak zdobyć wszystkie zasoby: tj. zaczynając od któregokolwiek. /' or ”lub ./żaden z nich faktycznie nie działał
javadba
3

Z wiosną jest to łatwe. Niezależnie od tego, czy jest to plik, folder, czy nawet wiele plików, istnieje szansa, że ​​możesz to zrobić przez wstrzyknięcie.

Ten przykład pokazuje wstrzyknięcie wielu plików znajdujących się w x/y/zfolderze.

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

Działa dla zasobów w systemie plików, a także w plikach JAR.

naXa
źródło
2
Chcę uzyskać nazwy folderów w katalogu / BOOT-INF / klas / static / opowieści. Próbuję kodu z @Value („classpath: static / opowieści / *”) i zwraca pustą listę po uruchomieniu JAR. Działa dla zasobów w ścieżce klasy, ale nie wtedy, gdy znajdują się one w pliku JAR.
PS
@Value ("classpath: x / y / z / *") private Resource [] zasoby; zrobił mi lewę. Miej na to godziny wyszukiwania! Dzięki!
Rudolf Schmidt
3

To powinno zadziałać (jeśli wiosna nie jest opcją):

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
źródło
Otrzymało to ostatnio opinię negatywną - jeśli napotkałeś problem, podziel się, dziękuję!
fl0w
1
Dziękuję @ArnoUnkrig - podziel się swoim zaktualizowanym rozwiązaniem, jeśli chcesz
fl0w
3

Mój sposób, bez sprężyny, używany podczas testu jednostkowego:

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
źródło
nie działa po uruchomieniu jar: java.nio.file.FileSystemNotFoundException at Paths.get (uri)
error1009
0

Myślę, że możesz to zrobić przy użyciu [ Zip System System Provider ] [1]. Podczas używania FileSystems.newFileSystemwygląda na to, że możesz traktować obiekty w tym pliku ZIP jako „zwykły” plik.

W powiązanej dokumentacji powyżej:

Określ opcje konfiguracji systemu plików zip w obiekcie java.util.Map przekazanym do FileSystems.newFileSystemmetody. Zobacz temat [Właściwości systemu plików Zip] [2], aby uzyskać informacje na temat właściwości konfiguracji specyficznych dla dostawcy dla systemu plików Zip.

Gdy masz już instancję systemu plików zip, możesz wywoływać metody klas [ java.nio.file.FileSystem] [3] i [ java.nio.file.Path] [4] w celu wykonywania operacji takich jak kopiowanie, przenoszenie i zmiana nazw plików, a także modyfikowanie atrybutów plików.

Dokumentacja jdk.zipfsmodułu w [stanach Java 11] [5]:

Dostawca systemu plików zip traktuje plik zip lub JAR jako system plików i zapewnia możliwość manipulowania zawartością pliku. Dostawca systemu plików zip można utworzyć za pomocą [ FileSystems.newFileSystem] [6], jeśli jest zainstalowany.

Oto wymyślony przykład, który wykorzystałem przy użyciu przykładowych zasobów. Zauważ, że a .zipjest a .jar, ale możesz dostosować swój kod, aby zamiast tego używał zasobów ścieżki klasy:

Ustawiać

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

Jawa

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

}

Wynik

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
źródło
0

Żadna z odpowiedzi nie działała dla mnie, mimo że moje zasoby zostały umieszczone w folderach zasobów i postępowałem zgodnie z powyższymi odpowiedziami. Co zrobiło lewę to:

@Value("file:*/**/resources/**/schema/*.json")
private Resource[] resources;
kukis
źródło
-5

Na podstawie powyższych informacji @rob stworzyłem implementację, którą wypuszczam do domeny publicznej:

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
}

Chociaż nie spodziewałbym getResourcesAsStreamsię, że będę tak działać w katalogu, to naprawdę działa i działa dobrze.

Deven Phillips
źródło