Obtenga una lista de recursos del directorio classpath

247

Estoy buscando una manera de obtener una lista de todos los nombres de recursos de un directorio classpath dado, algo así como un método List<String> getResourceNames (String directoryName).

Por ejemplo, dado un directorio de ruta de clases x/y/zque contiene archivos a.html, b.html, c.htmly un subdirectorio d, getResourceNames("x/y/z")debe devolver una List<String>que contiene las siguientes cadenas: ['a.html', 'b.html', 'c.html', 'd'].

Debería funcionar tanto para recursos en el sistema de archivos como en frascos.

Sé que puedo escribir un fragmento rápido con Files, JarFilesy URLs, pero no quiero reinventar la rueda. Mi pregunta es, dadas las bibliotecas disponibles públicamente, ¿cuál es la forma más rápida de implementar getResourceNames? Las pilas Spring y Apache Commons son factibles.

viaclectic
fuente
1
Respuesta relacionada: stackoverflow.com/a/30149061/4102160
Cfx
Compruebe este proyecto, resuelve el escaneo de carpetas de recursos: github.com/fraballi/resources-folder-scanner
Felix Aballi

Respuestas:

165

Escáner personalizado

Implemente su propio escáner. Por ejemplo:

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

Uso PathMatchingResourcePatternResolverde Spring Framework.

Reflexiones de Ronmamo

Las otras técnicas pueden ser lentas en tiempo de ejecución para grandes valores de CLASSPATH. Una solución más rápida es utilizar la API Reflections de ronmamo , que precompila la búsqueda en el momento de la compilación.

iirekm
fuente
12
si usa Reflections, todo lo que realmente necesita:new Reflections("my.package", new ResourcesScanner()).getResources(pattern)
zapp
27
¿La primera solución funciona cuando se ejecuta desde un archivo jar? Para mí, no lo hace. Puedo leer el archivo de esta manera, pero no puedo leer el directorio.
Valentina Chumak el
55
El primer método descrito en esta respuesta: leer la ruta como un recurso, no parece funcionar cuando los recursos están en el mismo JAR que el código ejecutable, al menos con OpenJDK 1.8. El error es bastante extraño: se lanza una NullPointerException desde lo profundo de la lógica de procesamiento de archivos de la JVM. Es como si los diseñadores realmente no anticiparan este uso de recursos, y solo hay una implementación parcial. Incluso si funciona en algunos JDK o entornos, este no parece ser un comportamiento documentado.
Kevin Boone
77
No puede leer un directorio como este, ya que no hay un directorio sino secuencias de archivos. Esta respuesta está medio equivocada.
Thomas Decaux el
44
El primer método no parece funcionar, al menos cuando se ejecuta desde el entorno de desarrollo, antes de agotar los recursos. La llamada a getResourceAsStream()con una ruta relativa a un directorio conduce a un FileInputStream (File), que se define para lanzar FileNotFoundException si el archivo es un directorio.
Andy Thomas
52

Aquí está el código
Fuente : 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 está utilizando Spring Eche un vistazo a PathMatchingResourcePatternResolver

Jigar Joshi
fuente
3
El interlocutor sabe cómo implementarlo utilizando clases estándar de Java, pero quiere saber cómo puede utilizar las bibliotecas existentes (Spring, Apache Commons).
Jeroen Rosenberg el
@Jeroen Rosenberg También hay otra forma dada que finalmente se ha seleccionado :)
Jigar Joshi
77
Considero que esta solución es útil para los casos en los que no usa Spring; sería ridículo agregar una dependencia de Spring solo por esta característica. ¡Así que gracias! "Sucio", pero útil :) PS Uno podría querer usar File.pathSeparatoren vez del disco con código :de getResourcesmétodo.
Timur
55
El ejemplo de código sistema depende, utilice este lugar: final String[] classPathElements = classPath.split(System.pathSeparator);
dcompiled
1
Advertencia: la propiedad del sistema java.class.path puede no contener el classpath real con el que se está ejecutando. Alternativamente, puede mirar el cargador de clases e interrogarlo para saber desde qué URL está cargando clases. La otra advertencia, por supuesto, es que si está haciendo esto bajo un SecurityManager, el constructor ZipFile puede rechazar su acceso al archivo, por lo que debería estar entendiendo eso.
Trejkaz
24

Usando reflexiones

Obtén todo en el classpath:

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

Otro ejemplo: obtenga todos los archivos con extensión .csv de some.package :

Reflections reflections = new Reflections("some.package", new ResourcesScanner());
Set<String> fileNames = reflections.getResources(Pattern.compile(".*\\.csv"));
NS du Toit
fuente
¿Hay alguna manera de lograr lo mismo sin tener que importar la dependencia de google? Me gustaría evitar tener esta dependencia solo para realizar esta tarea.
Enrico Giurin
1
Reflections no es una biblioteca de Google.
Luke Hutchison el
Tal vez fue en el pasado. Mi respuesta es de 2015 y encontré alguna referencia a la biblioteca usando la frase "Reflexiones de Google" antes de 2015. Veo que el código se movió un poco y se movió de code.google.com aquí a github aquí
NS du Toit
@NSduToit probablemente fue un artefacto de alojar el código en Google Code (no es un error poco común), no un reclamo de autoría por parte de Google.
mate b
17

Si usa apache commonsIO, puede usarlo para el sistema de archivos (opcionalmente con filtro de extensión):

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

y para recursos / classpath:

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

Si no sabe si "directoy /" está en el sistema de archivos o en los recursos, puede agregar un

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

o

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

antes de las llamadas y usar ambos en combinación ...

Robar
fuente
23
Files! = Resources
djechlin
3
Claro, los recursos no siempre pueden ser archivos, pero la pregunta era sobre los recursos que se originan en archivos / directorios. Por lo tanto, use el código de ejemplo si desea verificar una ubicación diferente, por ejemplo, si tiene un config.xml en sus recursos para alguna configuración predeterminada y debería ser posible cargar un config.xml externo si existe ...
Rob
2
¿Y por qué los recursos no son archivos? Los recursos son archivos archivados en un archivo zip. Esos se cargan en la memoria y se accede a ellos de manera específica, pero no puedo ver por qué no son archivos. ¿Algún ejemplo de recurso que no sea 'un archivo dentro del archivo'?
Mike
10
No funciona (NullPointerException) cuando el recurso de destino es un directorio que reside dentro de un archivo JAR (es decir, el JAR contiene la aplicación Principal y el directorio de destino)
Carlitos Way
12

Entonces, en términos de PathMatchingResourcePatternResolver, esto es lo que se necesita en el código:

@Autowired
ResourcePatternResolver resourceResolver;

public void getResources() {
  resourceResolver.getResources("classpath:config/*.xml");
}
Pavel Kotlov
fuente
solo una pequeña adición si desea buscar todos los recursos posibles en todos los classpath que necesita usarclasspath*:config/*.xml
revau.lt
5

El Spring framework's PathMatchingResourcePatternResolveres realmente increíble para estas cosas:

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

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

Dependencia de Maven:

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

Usó una combinación de la respuesta 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
fuente
cómo obtener todos los recursos: es decir, comenzar con cualquiera /' or . 'o ./ninguno de los cuales realmente funcionó
javadba
3

Con Spring es fácil. Ya sea un archivo o carpeta, o incluso varios archivos, hay posibilidades de que pueda hacerlo mediante inyección.

Este ejemplo demuestra la inyección de múltiples archivos ubicados en la x/y/zcarpeta.

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

Funciona para recursos en el sistema de archivos, así como en JAR.

naXa
fuente
2
Quiero obtener los nombres de las carpetas en / BOOT-INF / classes / static / stories. Intento su código con @Value ("classpath: static / stories / *") y devuelve una lista vacía cuando ejecuta el JAR. Funciona para recursos en el classpath, pero no cuando están en el JAR.
PS
@Value ("classpath: x / y / z / *") recursos privados [] recursos; me hizo el truco. Tener horas de búsqueda para eso! ¡Gracias!
Rudolf Schmidt
3

Esto debería funcionar (si la primavera no es una opción):

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
fuente
Esto recibió un voto negativo recientemente: si encontró un problema con él, por favor comparta, ¡gracias!
fl0w
1
Gracias @ArnoUnkrig - por favor comparta su solución actualizada si lo desea
fl0w
3

Mi manera, no Spring, utilizada durante una prueba unitaria:

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
fuente
no funciona, cuando ejecuta un jar: java.nio.file.FileSystemNotFoundException en Paths.get (uri)
error1009
0

Creo que puede aprovechar el [ Proveedor del sistema de archivos Zip ] [1] para lograr esto. Cuando se usa FileSystems.newFileSystemparece que puede tratar los objetos en ese ZIP como un archivo "normal".

En la documentación vinculada arriba:

Especifique las opciones de configuración para el sistema de archivos zip en el objeto java.util.Map pasado al FileSystems.newFileSystemmétodo. Consulte el tema [Propiedades del sistema de archivos zip] [2] para obtener información sobre las propiedades de configuración específicas del proveedor para el sistema de archivos zip.

Una vez que tenga una instancia de un sistema de archivos zip, puede invocar los métodos de las clases [ java.nio.file.FileSystem] [3] y [ java.nio.file.Path] [4] para realizar operaciones como copiar, mover y renombrar archivos, así como modificar los atributos del archivo.

La documentación para el jdk.zipfsmódulo en [Java 11 estados] [5]:

El proveedor del sistema de archivos zip trata un archivo zip o JAR como un sistema de archivos y proporciona la capacidad de manipular el contenido del archivo. El proveedor del sistema de archivos zip puede ser creado por [ FileSystems.newFileSystem] [6] si está instalado.

Aquí hay un ejemplo artificial que hice usando sus recursos de ejemplo. Tenga en cuenta que a .zipes a .jar, pero podría adaptar su código para utilizar recursos classpath:

Preparar

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

}

Salida

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

Ninguna de las respuestas funcionó para mí, aunque mis recursos se pusieron en carpetas de recursos y seguí las respuestas anteriores. Lo que hizo un truco fue:

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

Basado en la información de @rob anterior, creé la implementación que estoy lanzando al dominio 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
}

Si bien no hubiera esperado getResourcesAsStreamtrabajar así en un directorio, realmente funciona y funciona bien.

Deven Phillips
fuente