Obtenha uma lista de recursos do diretório classpath

247

Estou procurando uma maneira de obter uma lista de todos os nomes de recursos de um determinado diretório de caminho de classe, algo como um método List<String> getResourceNames (String directoryName).

Por exemplo, dado um diretório classpath x/y/zcontendo arquivos a.html, b.html, c.htmle um subdiretório d, getResourceNames("x/y/z")deve retornar um List<String>contendo as seguintes cadeias: ['a.html', 'b.html', 'c.html', 'd'].

Ele deve funcionar tanto para recursos no sistema de arquivos quanto em jars.

Eu sei que eu posso escrever um trecho rápido com Files, JarFiles e URLs, mas eu não quero reinventar a roda. Minha pergunta é, dadas as bibliotecas existentes publicamente disponíveis, qual é a maneira mais rápida de implementar getResourceNames? As pilhas Spring e Apache Commons são viáveis.

viaclectic
fonte
1
Resposta relacionada: stackoverflow.com/a/30149061/4102160
Cfx
Verifique este projeto e resolva a digitalização de pastas de recursos: github.com/fraballi/resources-folder-scanner
Felix Aballi

Respostas:

165

Scanner personalizado

Implemente seu próprio scanner. Por exemplo:

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

Use a PathMatchingResourcePatternResolverpartir do Spring Framework.

Reflexões de Ronmamo

As outras técnicas podem ser lentas no tempo de execução para valores enormes de CLASSPATH. Uma solução mais rápida é usar a API de reflexões da ronmamo , que pré-compila a pesquisa em tempo de compilação.

iirekm
fonte
12
se estiver usando Reflexões, tudo que você realmente precisa:new Reflections("my.package", new ResourcesScanner()).getResources(pattern)
Zapp
27
A primeira solução funciona quando executada a partir do arquivo jar? Para mim - não. Eu posso ler o arquivo dessa maneira, mas não consigo ler o diretório.
Valentina Chumak
5
O primeiro método descrito nesta resposta - lendo o caminho como um recurso, parece não funcionar quando os recursos estão no mesmo JAR que o código executável, pelo menos com o OpenJDK 1.8. A falha é bastante estranha - uma NullPointerException é lançada do fundo da lógica de processamento de arquivos da JVM. É como se os projetistas não tivessem realmente antecipado esse uso de recursos, e há apenas uma implementação parcial. Mesmo que funcione em alguns JDKs ou ambientes, isso não parece ser um comportamento documentado.
Kevin Boone
7
Você não pode ler um diretório como este, pois não há diretório, mas fluxos de arquivos. Esta resposta está meio errada.
Thomas Decaux
4
O primeiro método parece não funcionar - pelo menos ao executar no ambiente de desenvolvimento, antes de aumentar os recursos. A chamada para getResourceAsStream()com um caminho relativo para um diretório leva a um FileInputStream (File), que é definido para lançar FileNotFoundException se o arquivo for um diretório.
Andy Thomas
52

Aqui está o código
Fonte : 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);
        }
    }
}  

Se você estiver usando o Spring, dê uma olhada no PathMatchingResourcePatternResolver

Jigar Joshi
fonte
3
Questionador sabe como implementá-lo usando classes java padrão, mas deseja saber como pode utilizar as bibliotecas existentes (Spring, Apache Commons).
Jeroen Rosenberg
@Jeroen Rosenberg Há também uma outra maneira dado que foi selecionado finalmente :)
Jigar Joshi
7
Acho essa solução útil para casos em que você não usa o Spring - seria ridículo adicionar uma dependência do Spring apenas por esse recurso. Então obrigado! "Sujo", mas útil :) O PS One pode querer usar em File.pathSeparatorvez de codificado :no getResourcesmétodo.
Timur
5
O exemplo de código é dependente do sistema, use-o: final String[] classPathElements = classPath.split(System.pathSeparator);
dcompiled
1
Advertência: A propriedade do sistema java.class.path pode não conter o caminho de classe real no qual você está executando. Como alternativa, você pode olhar para o carregador de classes e interrogá-lo para quais URLs está carregando as classes. A outra ressalva, é claro, é que, se você estiver fazendo isso em um SecurityManager, o construtor ZipFile poderá rejeitar seu acesso ao arquivo, portanto, você deve entender isso.
21330 Trejkaz
24

Usando reflexões

Obtenha tudo no caminho de classe:

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

Outro exemplo - obtenha todos os arquivos com extensão .csv em some.package :

Reflections reflections = new Reflections("some.package", new ResourcesScanner());
Set<String> fileNames = reflections.getResources(Pattern.compile(".*\\.csv"));
NS du Toit
fonte
existe alguma maneira de conseguir o mesmo sem precisar importar a dependência do google? Eu gostaria de evitar ter essa dependência apenas para executar esta tarefa.
Enrico Giurin 5/03/19
1
O Reflections não é uma biblioteca do Google.
Luke Hutchison
Talvez tenha sido no passado. Minha resposta é de 2015 e encontrei alguma referência à biblioteca usando a frase "Google Reflections" antes de 2015. Vejo que o código mudou um pouco e passou de code.google.com aqui para o github aqui
NS du Toit
@NSduToit que provavelmente foi um artefato de hospedagem do código no Google Code (não é um erro incomum), não é uma reivindicação de autoria do Google.
matt b
17

Se você usar o apache commonsIO, poderá usar o sistema de arquivos (opcionalmente com o filtro de extensão):

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

e para recursos / caminho de classe:

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

Se você não souber se "diretório /" está no sistema de arquivos ou nos recursos, você pode adicionar um

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

ou

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

antes das chamadas e use ambos em combinação ...

Roubar
fonte
23
Arquivos! = Recursos
djechlin
3
Claro, os recursos nem sempre podem ser arquivos, mas a pergunta era sobre recursos originários de arquivos / diretórios. Portanto, use o código de exemplo se desejar verificar um local diferente, por exemplo, se você possui um config.xml em seus recursos para alguma configuração padrão e deve ser possível carregar um config.xml externo em vez disso, se ele existir ...
Rob
2
E por que recursos não são arquivos? Os recursos são arquivos arquivados no arquivo zip. Esses são carregados na memória e acessados ​​de maneira específica, mas não vejo por que não são arquivos. Algum exemplo de recurso que não é 'um arquivo dentro do arquivo'?
Mike
10
Não funciona (NullPointerException) quando o recurso de destino é um diretório que reside dentro de um arquivo JAR (ou seja, o JAR contém o aplicativo principal e o diretório de destino)
Carlitos Way
12

Portanto, em termos de PathMatchingResourcePatternResolver, é isso que é necessário no código:

@Autowired
ResourcePatternResolver resourceResolver;

public void getResources() {
  resourceResolver.getResources("classpath:config/*.xml");
}
Pavel Kotlov
fonte
apenas uma pequena adição, se você quiser procurar todos os recursos possíveis em todo o classpath você precisa usarclasspath*:config/*.xml
revau.lt
5

Os Spring framework's PathMatchingResourcePatternResolveré realmente impressionante para estas coisas:

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

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

Dependência do Maven:

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

Usou uma combinação da resposta 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
fonte
como obter todos os recursos: ou seja, começando com /' or .` ou ./nenhum dos quais realmente funcionou
javadba 5/01
3

Com a Primavera é fácil. Seja um arquivo, uma pasta ou até vários arquivos, há chances de que você possa fazer isso por injeção.

Este exemplo demonstra a injeção de vários arquivos localizados na x/y/zpasta

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

Ele funciona para recursos no sistema de arquivos e também em JARs.

naXa
fonte
2
Quero obter os nomes das pastas em / BOOT-INF / classes / static / stories. Eu tento seu código com @Value ("classpath: static / stories / *") e ele retorna uma lista vazia ao executar o JAR. Ele funciona para recursos no caminho de classe, mas não quando eles estão no JAR.
PS
@Value ("caminho da classe: x / y / z / *") private Resource []; fez o truque para mim. Tenha horas de pesquisa para isso! Obrigado!
Rudolf Schmidt
3

Isso deve funcionar (se a primavera não for uma opção):

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
fonte
Isso recebeu um voto negativo recentemente - se você encontrou um problema, compartilhe, obrigado!
Fl0w 14/08/19
1
Obrigado @ArnoUnkrig - por favor, compartilhe sua solução atualizada se você gosta
fl0w
3

Do meu jeito, no Spring, usado durante um teste de unidade:

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
fonte
não funciona, quando você executa um jar: java.nio.file.FileSystemNotFoundException at Paths.get (uri)
error1009
0

Eu acho que você pode aproveitar o [ Fornecedor do sistema de arquivos zip ] [1] para conseguir isso. Ao usarFileSystems.newFileSystem lo, parece que você pode tratar os objetos nesse ZIP como um arquivo "regular".

Na documentação vinculada acima:

Especifique as opções de configuração para o sistema de arquivos zip no objeto java.util.Map passado para o FileSystems.newFileSystem método Consulte o tópico [Propriedades do sistema de arquivos zip] [2] para obter informações sobre as propriedades de configuração específicas do provedor para o sistema de arquivos zip.

Depois de ter uma instância de um sistema de arquivos zip, você pode invocar os métodos das classes [ java.nio.file.FileSystem] [3] e [ java.nio.file.Path] [4] para executar operações como copiar, mover e renomear arquivos, bem como modificar os atributos do arquivo.

A documentação para o jdk.zipfsmódulo em [Java 11 states] [5]:

O provedor do sistema de arquivos zip trata um arquivo zip ou JAR como um sistema de arquivos e fornece a capacidade de manipular o conteúdo do arquivo. O provedor do sistema de arquivos zip pode ser criado por [ FileSystems.newFileSystem] [6], se instalado.

Aqui está um exemplo artificial que eu fiz usando seus recursos de exemplo. Observe que a .zipé a .jar, mas você pode adaptar seu código para usar os recursos do caminho de classe:

Configuração

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

}

Resultado

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

Nenhuma das respostas funcionou para mim, embora eu tenha meus recursos colocados em pastas de recursos e seguido as respostas acima. O que fez um truque foi:

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

Com base nas informações de @rob acima, criei a implementação que estou lançando para o domínio público:

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
}

Embora eu não esperasse getResourcesAsStreamtrabalhar assim em um diretório, ele realmente funciona e funciona bem.

Deven Phillips
fonte