2009-09-15 17 views
88

Ho questo codice che legge tutti i file da una directory.Come elenco i file all'interno di un file JAR?

File textFolder = new File("text_directory"); 

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

Funziona alla grande. Riempie l'array con tutti i file che terminano con ".txt" dalla directory "text_directory".

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

Quindi quello che voglio fare è, per elencare tutte le immagini all'interno del mio file JAR, in modo che io possa caricare con:

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

(Quello funziona perché il "companylogo" è "hardcoded" ma il numero di immagini all'interno del file JAR potrebbe essere da 10 a 200 di lunghezza variabile)

EDIT

Quindi credo che il mio problema principale sarebbe:. Come conoscere il nome del file JAR dove vive la mia classe principale?

Concesso Potrei leggerlo utilizzando java.util.Zip.

mia struttura è simile a questo:

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 

In questo momento sono in grado di caricare per 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.

+0

Solo un pensiero - perchè non immagini zip/vaso in un file separato e leggere le voci in essa dalla classe in un altro vaso? –

+2

Perché richiederebbe un passaggio "extra" per la distribuzione/installazione. :(Sai, gli utenti finali – OscarRyz

+0

Beh, potrei sbagliarmi, ma i barattoli possono essere incorporati in altri barattoli La soluzione di packaging one-jar (TM) http://www.ibm.com/developerworks/java/library/j -onejar/funziona su questa base.Tranne che nel tuo caso non hai bisogno dell'abilità di caricare le classi. –

risposta

77
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 spostamento e filtraggio della directory di NIO per effettuare la ricerca. Ciò renderebbe più semplice scrivere codice che gestisca i JAR e le directory "esplose".

+0

hey grazie ... cercavo un modo per farlo ora da qualche ora !! – Newtopian

+8

Sì, questo codice funziona se vogliamo elencare tutte le voci all'interno di questo file jar, ma se voglio solo farlo elenca una sottodirectory all'interno del jar, ad esempio ** example.jar/dir1/dir2/**, come posso elencare direttamente tutti i file all'interno di questa sottodirectory? O devo decomprimere questo file jar? Apprezzo molto il tuo aiuto! –

+0

Vedi anche http://stackoverflow.com/a/5194002/603516 – Vadzim

3

Un file jar è solo un file zip con un manifest strutturato. È possibile aprire il file jar con i soliti strumenti java zip ed eseguire la scansione del contenuto del file in questo modo, gonfiare i flussi, ecc. Quindi utilizzarlo in una chiamata getResourceAsStream e dovrebbe essere tutto hunky dory.

EDIT/dopo chiarimento

Mi c'è voluto un minuto per ricordare tutti i pezzi e sono sicuro che ci sono modi più puliti per farlo, ma ho voluto vedere che non ero pazzo. Nel mio progetto image.jpg è un file in una parte del file jar principale. Prendo il class loader della classe principale (SomeClass è il punto di ingresso) e lo uso per scoprire la risorsa image.jpg. Quindi un po 'di streaming magico per farlo entrare in questa cosa 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 
+0

Questo barattolo contiene il programma principale e le risorse. Come faccio a fare riferimento al barattolo di se stessi? dall'interno del file jar? – OscarRyz

+0

Per fare riferimento al file JAR, basta usare "blah.JAR" come String. Puoi usare 'new File (" blah.JAR ")' per creare un oggetto File che rappresenta il JAR, per esempio. Basta sostituire "blah.JAR" con il nome del tuo JAR. –

+0

Se è lo stesso jar che stai già esaurendo, il programma di caricamento classi dovrebbe essere in grado di vedere cose all'interno del barattolo ... Ho frainteso ciò che stavi cercando di fare inizialmente. – Mikeb

2

Dato un file JAR vero e proprio, è possibile elencare il contenuto utilizzando JarFile.entries(). Dovrai comunque conoscere la posizione del file JAR - non puoi semplicemente chiedere al classloader di elencare tutto ciò che potrebbe ottenere.

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 po 'approssimativo.

+0

Leggendo la tua risposta un'altra domanda sollevata. Cosa renderebbe la chiamata: this.getClass(). GetResource ("/ mia_directory"); Dovrebbe restituire un URL che potrebbe a sua volta essere .... usato come directory? Nahh ... fammi provare. – OscarRyz

+0

Sai sempre la posizione del JAR - è in ".". Finché il nome del JAR è noto per essere qualcosa, puoi usare una costante String da qualche parte. Ora, se le persone cambiano il nome del JAR ... –

+0

@Thomas: Questo presuppone che tu stia eseguendo l'app dalla directory corrente. Cosa c'è di sbagliato in "java -jar foo/bar/baz.jar"? –

4

Ecco un metodo che ho scritto per un "run all JUnits under a package". 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)); 
      } 
     } 
    } 
} 

Edit: Ah, in questo caso, si potrebbe desiderare questo frammento così (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); 
    } 
} 
+1

Credo che il grosso problema è conoscere il nome del file jar in primo luogo. Main-Class: lives. – OscarRyz

5

Quindi credo che il mio problema principale sarebbe, come conoscere il nome del vaso dove i miei principali vita di classe.

Supponendo che il progetto è confezionato in un vaso (non è necessariamente vero!), È possibile utilizzare ClassLoader.getResource() o FindResource() con il nome della classe (seguito da .class) per ottenere il vaso che contiene una determinata classe. Dovrai analizzare il nome del vaso 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 è parte di un barattolo.

+0

huh - interessante che questo sarebbe stato modificato senza commento ... Usiamo la tecnica sopra tutto il tempo e funziona bene. –

+0

Un vecchio problema, ma per me questo sembra un bel compromesso. Upvoted a zero :) –

+0

Upvoted, perché questa è l'unica soluzione elencata qui per il caso in cui una classe non ha un 'CodeSource'. – studro

18

Erickson di answer ha funzionato perfettamente:

Ecco il codice di lavoro.

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

E ho appena modificare il mio metodo di caricamento da questo:

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

A tal:

String [] webimages = ... 

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

Qualche tempo fa ho fatto una funzione che riceve classess dall'interno 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; 
} 
48

codice che funziona per entrambi IDE e.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()); 
     } 
    } 
} 
+5

'FileSystems.newFileSystem()' prende una 'Mappa ', quindi è necessario specificare a 'Collections.emptyMap()' che è necessario restituirne uno opportunamente digitato. Funziona: 'Collezioni. emptyMap()'. – Zero3

+4

Fantastico !!! ma URI uri = MyClass.class.getResource ("/ resources"). toURI(); dovrebbe avere MyClass.class.getClassLoader(). getResource ("/ resources"). toURI(); io, getClassLoader(). Altrimenti non funzionava per me. – EMM

+4

Non dimenticare di chiudere 'fileSystem'! – gmjonker

0

solo un modo diverso di quotazione/lettura di file da un URL vaso e lo fa in modo ricorsivo per i vasi annidati

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

Ecco un esempio di utilizzo Reflections libreria per scansionare ricorsivamente classpath per modello di nomi di espressioni regolari accresciuto con un paio di Guava perks per recuperare il contenuto delle risorse:

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

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

Questo funziona con entrambi i vasi e classi esplose.

5

vorrei approfondire acheron55 di answer, dal momento che si tratta di una soluzione molto non sicuro, per diverse ragioni:

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

Questo è un po 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 c'è alcuna reale necessità di sincronizzare il nome del file; si potrebbe semplicemente sincronizzare sullo stesso oggetto ogni volta (o fare il metodo synchronized), è puramente un'ottimizzazione.

Direi che questa è ancora una soluzione problematica, dal momento che 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 (per esempio, su getClass().getResource().

Questo particolare interfaccia Java NIO è una specie di orribile, in quanto introduce una risorsa globale/Singleton non thread-safe, e la sua documentazione è estremamente vago (molte incognite dovute a implementazioni specifiche del provider). I risultati possono variare per altri provider FileSystem (non JAR). Forse c'è una buona ragione perché è così, non so, non ho studiato le implementazioni

+0

La sincronizzazione di risorse esterne, come le FS, non ha molto senso all'interno di una VM. Possono esserci altre applicazioni che accedono al di fuori della VM. Inoltre, anche all'interno della propria applicazione, il blocco basato su nomi di file può essere facilmente aggirato. Con queste cose è meglio fare affidamento sui meccanismi di sincronizzazione del SO, come il blocco dei file. – Espinosa

+0

@Espinosa Il meccanismo di blocco nome-file potrebbe essere completamente bypassato; la mia risposta non è abbastanza sicura, ma credo che sia il massimo che si possa ottenere con Java NIO con il minimo sforzo. Affidarsi al sistema operativo per gestire i blocchi o non avere il controllo di quali applicazioni accedono ai file è una cattiva pratica IMHO, a meno che non si stia costruendo un'app basata sul cliente, ad esempio un editor di testo. La mancata gestione dei blocchi da solo causerà l'emissione di eccezioni o causerà il blocco dei thread dell'applicazione - entrambi dovrebbero essere evitati. –

0

Ho portato acheron55's answer a Java 7 e chiuso l'oggetto FileSystem. Questo codice funziona negli IDE, nei file jar e in un jar all'interno di una guerra su Tomcat 7, ma non e che lo sia non lavoro in un barattolo all'interno di una guerra su JBoss 7 (fornisce FileSystemNotFoundException: Provider "vfs" not installed, vedere anche this post). Inoltre, come il codice originale, non è thread-safe, come suggerito da errr. Per questi motivi ho abbandonato questa soluzione; tuttavia, se si può accettare questi problemi, qui è il mio codice ready-made:

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