Pregunta

Tengo este código que lee todos los archivos de un directorio.

    File textFolder = new File("text_directory");

    File [] texFiles = textFolder.listFiles( new FileFilter() {
           public boolean accept( File file ) {
               return file.getName().endsWith(".txt");
           }
    });

Funciona muy bien. Llena la matriz con todos los archivos que terminan en " .txt " del directorio " text_directory " ;.

¿Cómo puedo leer el contenido de un directorio de manera similar dentro de un archivo JAR?

Entonces, lo que realmente quiero hacer es enumerar todas las imágenes dentro de mi archivo JAR, para poder cargarlas con:

ImageIO.read(this.getClass().getResource("CompanyLogo.png"));

(Eso funciona porque el "CompanyLogo" está "codificado" pero el número de imágenes dentro del archivo JAR podría ser de 10 a 200 de longitud variable).

EDIT

Entonces, supongo que mi problema principal sería: ¿Cómo saber el nombre del archivo JAR donde vive mi clase principal?

De acuerdo, podría leerlo usando java.util.Zip .

Mi estructura es así:

Son como:

my.jar!/Main.class
my.jar!/Aux.class
my.jar!/Other.class
my.jar!/images/image01.png
my.jar!/images/image02a.png
my.jar!/images/imwge034.png
my.jar!/images/imagAe01q.png
my.jar!/META-INF/manifest 

Ahora mismo puedo cargar por ejemplo " images / image01.png " utilizando:

    ImageIO.read(this.getClass().getResource("images/image01.png));

Pero solo porque conozco el nombre del archivo, para el resto tengo que cargarlos dinámicamente.

¿Fue útil?

Solución

CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
if (src != null) {
  URL jar = src.getLocation();
  ZipInputStream zip = new ZipInputStream(jar.openStream());
  while(true) {
    ZipEntry e = zip.getNextEntry();
    if (e == null)
      break;
    String name = e.getName();
    if (name.startsWith("path/to/your/dir/")) {
      /* Do something with this entry. */
      ...
    }
  }
} 
else {
  /* Fail... */
}

Tenga en cuenta que en Java 7, puede crear un FileSystem a partir del archivo JAR (zip), y luego utilizar los mecanismos de anotación y filtrado del directorio de NIO para buscar a través de él. Esto facilitaría la escritura de código que maneja JAR y "explotó". directorios.

Otros consejos

Código que funciona tanto para IDE como para archivos .jar:

import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;

public class ResourceWalker {
    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        Path myPath;
        if (uri.getScheme().equals("jar")) {
            FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap());
            myPath = fileSystem.getPath("/resources");
        } else {
            myPath = Paths.get(uri);
        }
        Stream<Path> walk = Files.walk(myPath, 1);
        for (Iterator<Path> it = walk.iterator(); it.hasNext();){
            System.out.println(it.next());
        }
    }
}
La

respuesta funcionó perfectamente:

Aquí está el código de trabajo.

CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
List<String> list = new ArrayList<String>();

if( src != null ) {
    URL jar = src.getLocation();
    ZipInputStream zip = new ZipInputStream( jar.openStream());
    ZipEntry ze = null;

    while( ( ze = zip.getNextEntry() ) != null ) {
        String entryName = ze.getName();
        if( entryName.startsWith("images") &&  entryName.endsWith(".png") ) {
            list.add( entryName  );
        }
    }

 }
 webimages = list.toArray( new String[ list.size() ] );

Y acabo de modificar mi método de carga a partir de esto:

File[] webimages = ... 
BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex].getName() ));

A esto:

String  [] webimages = ...

BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));

Me gustaría expandirme en respuesta , ya que es una solución muy poco segura, por varias razones:

  1. No cierra el objeto FileSystem .
  2. No comprueba si el objeto FileSystem ya existe.
  3. No es seguro para subprocesos.

Esta es una solución algo más segura:

private static ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();

public void walk(String path) throws Exception {

    URI uri = getClass().getResource(path).toURI();
    if ("jar".equals(uri.getScheme()) {
        safeWalkJar(path, uri);
    } else {
        Files.walk(Paths.get(path));
    }
}

private void safeWalkJar(String path, URI uri) throws Exception {

    synchronized (getLock(uri)) {    
        // this'll close the FileSystem object at the end
        try (FileSystem fs = getFileSystem(uri)) {
            Files.walk(fs.getPath(path));
        }
    }
}

private Object getLock(URI uri) {

    String fileName = parseFileName(uri);  
    locks.computeIfAbsent(fileName, s -> new Object());
    return locks.get(fileName);
}

private String parseFileName(URI uri) {

    String schemeSpecificPart = uri.getSchemeSpecificPart();
    return schemeSpecificPart.substring(0, schemeSpecificPart.indexOf("!"));
}

private FileSystem getFileSystem(URI uri) throws IOException {

    try {
        return FileSystems.getFileSystem(uri);
    } catch (FileSystemNotFoundException e) {
        return FileSystems.newFileSystem(uri, Collections.<String, String>emptyMap());
    }
}   

No hay necesidad real de sincronizar sobre el nombre del archivo; uno podría simplemente sincronizar en el mismo objeto cada vez (o hacer que el método sincronizado ), sea puramente una optimización.

Yo diría que esta sigue siendo una solución problemática, ya que puede haber otras partes en el código que usan la interfaz FileSystem sobre los mismos archivos, y podría interferir con ellos (incluso en un aplicación de un solo hilo).
Además, no busca null s (por ejemplo, en getClass (). GetResource () .

Esta interfaz particular de Java NIO es un poco horrible, ya que introduce un recurso global / singleton no seguro para subprocesos, y su documentación es extremadamente vaga (muchas incógnitas debido a implementaciones específicas del proveedor). Los resultados pueden variar para otros proveedores de FileSystem (no JAR). Tal vez hay una buena razón para que sea así; No sé, no he investigado las implementaciones.

Aquí hay un método que escribí para " ejecutar todos los JUnits bajo un paquete " ;. Debería poder adaptarlo a sus necesidades.

private static void findClassesInJar(List<String> classFiles, String path) throws IOException {
    final String[] parts = path.split("\\Q.jar\\\\E");
    if (parts.length == 2) {
        String jarFilename = parts[0] + ".jar";
        String relativePath = parts[1].replace(File.separatorChar, '/');
        JarFile jarFile = new JarFile(jarFilename);
        final Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            final JarEntry entry = entries.nextElement();
            final String entryName = entry.getName();
            if (entryName.startsWith(relativePath)) {
                classFiles.add(entryName.replace('/', File.separatorChar));
            }
        }
    }
}

Editar: Ah, en ese caso, es posible que también desee este fragmento (mismo caso de uso :))

private static File findClassesDir(Class<?> clazz) {
    try {
        String path = clazz.getProtectionDomain().getCodeSource().getLocation().getFile();
        final String codeSourcePath = URLDecoder.decode(path, "UTF-8");
        final String thisClassPath = new File(codeSourcePath, clazz.getPackage().getName().repalce('.', File.separatorChar));
    } catch (UnsupportedEncodingException e) {
        throw new AssertionError("impossible", e);
    }
}
  
    

Entonces, supongo que mi problema principal sería saber cómo saber el nombre del recipiente donde vive mi clase principal.

  

Suponiendo que su proyecto está empaquetado en un Jar (¡no necesariamente es cierto!), puede usar ClassLoader.getResource () o findResource () con el nombre de la clase (seguido de .class) para obtener el jar que contiene una clase dada . Tendrá que analizar el nombre del jar de la URL que se devuelve (no es tan difícil), que dejaré como ejercicio para el lector :-)

Asegúrese de probar el caso en el que la clase no es parte de un frasco.

Un archivo jar es solo un archivo zip con un manifiesto estructurado. Puede abrir el archivo jar con las herramientas habituales de Java Zip y escanear el contenido del archivo de esa manera, inflar secuencias, etc. Luego, usarlo en una llamada getResourceAsStream, y debería ser todo lo que necesita.

EDITAR / después de la aclaración

Me tomó un minuto recordar todas las partes y piezas y estoy seguro de que hay formas más limpias de hacerlo, pero quería ver que no estaba loco. En mi proyecto image.jpg es un archivo en alguna parte del archivo jar principal. Obtengo el cargador de clases de la clase principal (SomeClass es el punto de entrada) y lo uso para descubrir el recurso image.jpg. Luego, transmite magia para obtener esta imagen de ImageInputStream y todo está bien.

InputStream inputStream = SomeClass.class.getClassLoader().getResourceAsStream("image.jpg");
JPEGImageReaderSpi imageReaderSpi = new JPEGImageReaderSpi();
ImageReader ir = imageReaderSpi.createReaderInstance();
ImageInputStream iis = new MemoryCacheImageInputStream(inputStream);
ir.setInput(iis);
....
ir.read(0); //will hand us a buffered image

Dado un archivo JAR real, puede enumerar los contenidos usando JarFile.entries () . Sin embargo, necesitará saber la ubicación del archivo JAR; no puede simplemente pedirle al cargador de clases que enumere todo lo que podría obtener.

Debería poder calcular la ubicación del archivo JAR en función de la URL devuelta desde ThisClassName.class.getResource (" ThisClassName.class ") , pero puede ser un poco violín.

Hace algún tiempo hice una función que obtiene clases desde JAR:

public static Class[] getClasses(String packageName) 
throws ClassNotFoundException{
    ArrayList<Class> classes = new ArrayList<Class> ();

    packageName = packageName.replaceAll("\\." , "/");
    File f = new File(jarName);
    if(f.exists()){
        try{
            JarInputStream jarFile = new JarInputStream(
                    new FileInputStream (jarName));
            JarEntry jarEntry;

            while(true) {
                jarEntry=jarFile.getNextJarEntry ();
                if(jarEntry == null){
                    break;
                }
                if((jarEntry.getName ().startsWith (packageName)) &&
                        (jarEntry.getName ().endsWith (".class")) ) {
                    classes.add(Class.forName(jarEntry.getName().
                            replaceAll("/", "\\.").
                            substring(0, jarEntry.getName().length() - 6)));
                }
            }
        }
        catch( Exception e){
            e.printStackTrace ();
        }
        Class[] classesA = new Class[classes.size()];
        classes.toArray(classesA);
        return classesA;
    }else
        return null;
}

Aquí hay un ejemplo del uso de la biblioteca Reflections para escanear recursivamente classpath mediante un patrón de nombre regex aumentado con un par de Guava ventajas para obtener contenido de recursos:

Reflections reflections = new Reflections("com.example.package", new ResourcesScanner());
Set<String> paths = reflections.getResources(Pattern.compile(".*\\.template<*>quot;));

Map<String, String> templates = new LinkedHashMap<>();
for (String path : paths) {
    log.info("Found " + path);
    String templateName = Files.getNameWithoutExtension(path);
    URL resource = getClass().getClassLoader().getResource(path);
    String text = Resources.toString(resource, StandardCharsets.UTF_8);
    templates.put(templateName, text);
}

Esto funciona tanto con tarros como con clases explotadas.

He portado acheron55's responda a Java 7 y cierre el objeto FileSystem . Este código funciona en IDE, en archivos jar y en un jar dentro de una guerra contra Tomcat 7; pero tenga en cuenta que no funciona en un frasco dentro de una guerra contra JBoss 7 (da FileSystemNotFoundException: Proveedor " vfs " no instalado , vea también esta publicación ). Además, al igual que el código original, no es seguro para subprocesos, como lo sugiere errr . Por estas razones, he abandonado esta solución; sin embargo, si puede aceptar estos problemas, aquí está mi código listo para usar:

import java.io.IOException;
import java.net.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;

public class ResourceWalker {

    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        System.out.println("Starting from: " + uri);
        try (FileSystem fileSystem = (uri.getScheme().equals("jar") ? FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap()) : null)) {
            Path myPath = Paths.get(uri);
            Files.walkFileTree(myPath, new SimpleFileVisitor<Path>() { 
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    System.out.println(file);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }
}

Simplemente es una forma diferente de enumerar / leer archivos de una URL jar y lo hace de forma recursiva para jarras anidadas

https://gist.github.com/trung/2cd90faab7f75b3bcbaa

URL urlResource = Thead.currentThread().getContextClassLoader().getResource("foo");
JarReader.read(urlResource, new InputStreamCallback() {
    @Override
    public void onFile(String name, InputStream is) throws IOException {
        // got file name and content stream 
    }
});
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top