Jak wyświetlić listę plików w pliku JAR?

Mam ten kod, który odczytuje wszystkie pliki z katalogu.

    File textFolder = new File("text_directory");

    File [] texFiles = textFolder.listFiles( new FileFilter() {
           public boolean accept( File file ) {
               return file.getName().endsWith(".txt");
           }
    });
Działa świetnie. Wypełnia tablicę wszystkimi plikami, które kończą się ".txt "z katalogu "text_directory".

Jak mogę odczytać zawartość katalogu w podobny sposób w pliku JAR?

Więc tak naprawdę chcę zrobić listę wszystkich obrazów w moim pliku JAR, aby móc je załadować:

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

(ten działa, ponieważ "CompanyLogo" jest "hardcoded", ale liczba obrazów wewnątrz pliku JAR może wynosić od 10 do 200 o zmiennej długości.)

EDIT

Więc moim głównym problemem będzie: jak poznać nazwę pliku JAR gdzie mieszka moja główna klasa?

Przyznam, że mogłem to odczytać używając java.util.Zip.

Moja struktura jest taka:

Są jak:

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 

Teraz jestem w stanie załadować na przykład "images / image01.png " używając:

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

Ale tylko dlatego, że znam nazwę pliku, dla reszta muszę ładować je dynamicznie.

Author: Erick Robertson, 2009-09-15

13 answers

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

Zauważ, że w Javie 7 możesz utworzyć FileSystem z pliku JAR (zip), a następnie użyć mechanizmów wyszukiwania i filtrowania katalogów NIO do przeszukiwania go. Ułatwiłoby to pisanie kodu obsługującego jary i katalogi "eksplodujące".

 78
Author: erickson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-01 20:23:12

Kod, który działa zarówno dla IDE i .pliki 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());
        }
    }
}
 56
Author: acheron55,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-18 03:34:18

Odpowiedź Ericksona zadziałała idealnie:

Oto kod roboczy.
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() ] );

A ja właśnie zmodyfikowałem metodę load z tego:

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

Do tego:

String  [] webimages = ...

BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));
 19
Author: OscarRyz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:17:53

Chciałbym rozszerzyć odpowiedź acheron55 , ponieważ jest to bardzo Nie bezpieczne rozwiązanie, z kilku powodów:

  1. nie zamyka FileSystem obiektu.
  2. nie sprawdza, czy obiekt FileSystem już istnieje.
  3. to nie jest bezpieczne dla wątków.

Jest to nieco bezpieczniejsze rozwiązanie:

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

Nie ma potrzeby synchronizacji nad nazwą pliku, można po prostu synchronizować na tym samym obiekcie za każdym razem (lub zrobić metodę synchronized), to tylko optymalizacja.

Powiedziałbym, że jest to nadal problematyczne rozwiązanie, ponieważ mogą istnieć inne części kodu, które używają interfejsu FileSystem nad tymi samymi plikami, i może to zakłócać ich działanie (nawet w pojedynczej aplikacji z wątkiem).
Ponadto, nie sprawdza nulls (na przykład na getClass().getResource().

Ten konkretny interfejs Java NIO jest trochę okropny, ponieważ wprowadza globalny / singleton Nie bezpieczny dla wątków zasób, a jego dokumentacja jest bardzo niejasna (wiele niewiadomych ze względu na implementacje specyficzne dla dostawcy). Wyniki mogą się różnić dla innych dostawców FileSystem (nie JAR). Może jest ku temu dobry powód; Nie wiem, nie badałem implementacji.

 6
Author: Eyal Roth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:09
Więc myślę, że moim głównym problemem byłoby, jak poznać nazwę słoika, w którym mieszka moja główna klasa.

Zakładając, że twój projekt jest zapakowany w słoik (niekoniecznie prawda!), możesz użyć Classloadera.getResource() lub findResource () z nazwą klasy (po której następuje .class), aby uzyskać słoik, który zawiera daną klasę. Będziesz musiał przeanalizować nazwę jar z adresu URL, który zostanie zwrócony( nie taki trudny), który zostawię jako ćwiczenie dla czytelnika :-)

Pamiętaj, aby przetestować przypadek, w którym klasa nie jest częścią słoika.

 5
Author: Kevin Day,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-16 06:41:30

Oto metoda, którą napisałem dla "run all JUnits under a package". Powinieneś być w stanie dostosować go do swoich potrzeb.

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

Edytuj: Ah, w takim razie, możesz też chcieć ten fragment (ten sam przypadek użycia:))

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);
    }
}
 4
Author: Ran Biron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-15 19:51:50

Plik jar jest tylko plikiem zip ze strukturyzowanym manifestem. Możesz otworzyć plik JAR za pomocą zwykłych narzędzi Java zip i skanować zawartość pliku w ten sposób, nadmuchiwać strumienie itp. Następnie użyj tego w wywołaniu getResourceAsStream, i powinno być wszystko ładne dory.

Edycja / po wyjaśnieniu

Zajęło mi minutę, aby zapamiętać wszystkie kawałki i kawałki i jestem pewien, że są czystsze sposoby, aby to zrobić, ale chciałem zobaczyć, że nie jestem szalony. W moim projekcie.jpg to plik w jakiejś części głównego pliku jar. Dostaję class loader klasy głównej (SomeClass jest punktem wejścia) i używam go do odkrywania obrazu.zasoby jpg. Potem trochę magii strumienia, aby wprowadzić ją do tego imageinputstream i wszystko jest w porządku.

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
 3
Author: Mikeb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-15 21:01:21

Biorąc pod uwagę rzeczywisty plik JAR, możesz wyświetlić zawartość za pomocą JarFile.entries(). Musisz jednak znać lokalizację pliku JAR - nie możesz po prostu poprosić classloadera o listę wszystkiego, do czego mógłby się dostać.

Powinieneś być w stanie określić lokalizację pliku JAR na podstawie adresu URL zwróconego z ThisClassName.class.getResource("ThisClassName.class"), ale może to być trochę dziwne.

 3
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-15 11:57:22

Jakiś czas temu zrobiłem funkcję, która pobiera klasę z wnętrza Jara:

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;
}
 3
Author: berni,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-27 23:28:40

Oto przykład użycia Reflections library do rekurencyjnego skanowania classpath za pomocą wzorca nazw regex powiększonego o kilka atutówGuava do pobierania zawartości zasobów:

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

To działa zarówno ze słoikami, jak i klasami wybuchowymi.

 3
Author: Vadzim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-03 10:03:16
 1
Author: jgran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:29

Przeportowałem odpowiedź acheron55 do Java 7 i zamknąłem obiekt FileSystem. Ten kod działa w IDE, w plikach jar i w jar wewnątrz wojny na Tomcat 7; ale zauważ, że nie działa w jar wewnątrz wojny na JBoss 7 (podaje FileSystemNotFoundException: Provider "vfs" not installed, Zobacz także ten post ). Ponadto, podobnie jak oryginalny kod, nie jest bezpieczny dla wątków, jak sugeruje errr . Z tych powodów zrezygnowałem z tego rozwiązania; jednak, jeśli możesz zaakceptować te kwestie, oto mój gotowy kod:

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;
                }
            });
        }
    }
}
 1
Author: Pino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:25:55

Po prostu inny sposób wyświetlania / czytania plików z adresu URL jar i robi to rekurencyjnie dla zagnieżdżonych jarów

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 
    }
});
 0
Author: erolagnab,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-17 03:00:21