Domanda

Ho questo codice che legge tutti i file da una directory.

    File textFolder = new File("text_directory");

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

Funziona benissimo. Riempie l'array con tutti i file che terminano con " .txt " dalla directory " directory_directory " ;.

Come posso leggere il contenuto di una directory in modo simile all'interno di un file JAR?

Quindi quello che voglio davvero fare è elencare tutte le immagini all'interno del mio file JAR, in modo da poterle caricare con:

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

(Quello funziona perché "CompanyLogo" è "hardcoded", ma il numero di immagini all'interno del file JAR potrebbe essere compreso tra 10 e 200 di lunghezza variabile.)

Modifica

Quindi immagino che il mio problema principale sarebbe: come conoscere il nome del file JAR dove vive la mia classe principale?

Concesso che potrei leggerlo usando java.util.Zip .

La mia struttura è così:

Sono come:

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 

Al momento sono in grado di caricare ad esempio " images / image01.png " utilizzando:

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

Ma solo perché conosco il nome del file, per il resto devo caricarli dinamicamente.

È stato utile?

Soluzione

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... */
}

Si noti che in Java 7, è possibile creare un FileSystem dal file JAR (zip), quindi utilizzare i meccanismi di camminata e filtro della directory di NIO per cercarlo. Ciò semplificherebbe la scrittura di codice che gestisce JAR e "esploso" le directory.

Altri suggerimenti

Codice che funziona sia per i file IDE che per i file .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());
        }
    }
}

Vorrei espandere su acheron55's rispondi , poiché si tratta di una soluzione non sicura, per diversi motivi:

  1. Non chiude l'oggetto FileSystem .
  2. Non controlla se l'oggetto FileSystem esiste già.
  3. Non è thread-safe.

Questa è in qualche modo una soluzione più sicura:

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

Non è necessario sincronizzare il nome del file; si potrebbe semplicemente sincronizzare sullo stesso oggetto ogni volta (o rendere il metodo sincronizzato ), è puramente un'ottimizzazione.

Direi che questa è ancora una soluzione problematica, poiché potrebbero esserci altre parti nel codice che utilizzano l'interfaccia FileSystem sugli stessi file e potrebbero interferire con esse (anche in un applicazione a thread singolo).
Inoltre, non controlla null s (ad esempio, su getClass (). GetResource () .

Questa particolare interfaccia Java NIO è un po 'orribile, dal momento che introduce una risorsa globale / singleton non thread-safe, e la sua documentazione è estremamente vaga (molti sconosciuti a causa di implementazioni specifiche del provider). I risultati possono variare per altri provider FileSystem (non JAR). Forse c'è una buona ragione per essere così; Non lo so, non ho studiato le implementazioni.

Ecco un metodo che ho scritto per eseguire "tutte le JUnit in un pacchetto". Dovresti essere in grado di adattarlo alle tue esigenze.

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

Modifica: Ah, in quel caso, potresti desiderare anche questo frammento (stesso caso d'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);
    }
}
  
    

Quindi immagino che il mio problema principale sarebbe, come sapere il nome del barattolo in cui vive la mia classe principale.

  

Supponendo che il tuo progetto sia impacchettato in un Jar (non necessariamente vero!), puoi usare ClassLoader.getResource () o findResource () con il nome della classe (seguito da .class) per ottenere il jar che contiene una determinata classe . Dovrai analizzare il nome del barattolo dall'URL che viene restituito (non così difficile), che lascerò come esercizio per il lettore :-)

Assicurati di verificare il caso in cui la classe non fa parte di un vaso.

Un file jar è solo un file zip con un manifest strutturato. Puoi aprire il file jar con i soliti strumenti java zip e scansionare il contenuto del file in quel modo, gonfiare i flussi, ecc. Quindi usalo in una chiamata getResourceAsStream e dovrebbe essere tutto hunky dory.

MODIFICA / dopo chiarimento

Mi ci è voluto un minuto per ricordare tutti i pezzi e sono sicuro che ci sono modi più puliti per farlo, ma volevo vedere che non ero pazzo. Nel mio progetto image.jpg è un file in alcune parti del file jar principale. Ottengo il caricatore di classe della classe principale (SomeClass è il punto di ingresso) e lo uso per scoprire la risorsa image.jpg. Quindi un po 'di flusso magico per farlo entrare in questa cosa di ImageInputStream e tutto va bene.

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

Dato un vero file JAR, puoi elencare i contenuti usando JarFile.entries () . Dovrai però conoscere la posizione del file JAR - non puoi semplicemente chiedere al classloader di elencare tutto ciò che potrebbe arrivare.

Dovresti essere in grado di calcolare la posizione del file JAR in base all'URL restituito da ThisClassName.class.getResource (" ThisClassName.class ") , ma potrebbe essere un pochino poco pratici.

Qualche tempo fa ho creato una funzione che ottiene classe da dentro 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;
}

Ecco un esempio dell'uso della libreria Reflections per scansionare ricorsivamente il percorso di classe in base al modello di nome regex aumentato con una coppia di Guava consente di recuperare i contenuti delle risorse:

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

Funziona con entrambi i vasetti e le classi esplose.

Ho portato i acheron55's rispondere a Java 7 e chiudere l'oggetto FileSystem . Questo codice funziona negli IDE, nei file jar e in un jar all'interno di una guerra a Tomcat 7; ma nota che non funziona in un vaso all'interno di una guerra contro JBoss 7 (fornisce FileSystemNotFoundException: provider " vfs " non installato , vedi anche questo post ). Inoltre, come il codice originale, non è thread-safe, come suggerito da errr . Per questi motivi ho abbandonato questa soluzione; tuttavia, se puoi accettare questi problemi, ecco il mio codice pronto:

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

Solo un modo diverso di elencare / leggere i file da un URL jar e lo fa in modo ricorsivo per barattoli nidificati

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 
    }
});
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top