Как вывести список файлов внутри файла JAR? - PullRequest
105 голосов
/ 15 сентября 2009

У меня есть этот код, который читает все файлы из каталога.

    File textFolder = new File("text_directory");

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

Отлично работает. Он заполняет массив всеми файлами, заканчивающимися на «.txt» из каталога «text_directory».

Как я могу прочитать содержимое каталога аналогичным образом в файле JAR?

Итак, что я действительно хочу сделать, это перечислить все изображения внутри моего JAR-файла, чтобы я мог загрузить их с помощью:

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

(Это работает, потому что «CompanyLogo» является «жестко закодированным», но число изображений в файле JAR может быть от 10 до 200 переменной длины.)

EDIT

Итак, я думаю, что моей главной проблемой будет: Как узнать имя файла JAR , где живет мой основной класс?

Конечно, я мог прочитать это, используя java.util.Zip.

Моя структура такая:

Они похожи:

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 

Прямо сейчас я могу загрузить, например, «images / image01.png», используя:

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

Но только потому, что я знаю имя файла, в остальном я должен загружать их динамически.

Ответы [ 13 ]

84 голосов
/ 15 сентября 2009
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... */
}

Обратите внимание, что в Java 7 вы можете создать FileSystem из файла JAR (zip), а затем использовать механизмы обхода и фильтрации каталогов NIO для его поиска. Это упростит написание кода, который обрабатывает JAR-файлы и «взорванные» каталоги.

64 голосов
/ 21 января 2015

Код, который работает как для IDE, так и для файлов .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());
        }
    }
}
20 голосов
/ 17 сентября 2009

ответ Эриксона отлично работал:

Вот рабочий код.

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

И я только что изменил свой метод загрузки из этого:

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

К этому:

String  [] webimages = ...

BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));
8 голосов
/ 09 июня 2016

Я хотел бы остановиться на ответе acheron55 , поскольку это очень небезопасное решение по нескольким причинам:

  1. Не закрывает FileSystem объект.
  2. Он не проверяет, существует ли объект FileSystem.
  3. Это не потокобезопасно.

Это более безопасное решение:

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

Нет необходимости синхронизировать имя файла; каждый раз можно просто синхронизировать один и тот же объект (или сделать метод synchronized), это просто оптимизация.

Я бы сказал, что это все еще проблематичное решение, поскольку в коде могут присутствовать другие части, которые используют интерфейс FileSystem для тех же файлов, и это может им помешать (даже в однопоточном приложении).
Кроме того, он не проверяет null с (например, getClass().getResource().

Этот конкретный интерфейс Java NIO выглядит ужасно, поскольку он представляет глобальный / одноэлементный ресурс, не поддерживающий потоки, и его документация чрезвычайно расплывчата (много неизвестных из-за реализаций, специфичных для поставщика). Результаты могут отличаться для других FileSystem поставщиков (не JAR). Может быть, для этого есть веская причина; Я не знаю, я не исследовал реализации.

5 голосов
/ 16 сентября 2009

Так что я думаю, что моя главная проблема в том, как узнать название банки, в которой живет мой основной класс.

Предполагая, что ваш проект упакован в Jar (не обязательно true!), Вы можете использовать ClassLoader.getResource () или findResource () с именем класса (за которым следует .class), чтобы получить jar, содержащий данный класс. , Вам нужно будет проанализировать имя фляги по возвращаемому URL (не так сложно), которое я оставлю в качестве упражнения для читателя: -)

Обязательно проверьте случай, когда класс не является частью банки.

5 голосов
/ 15 сентября 2009

Вот метод, который я написал для "запуска всех JUnits в пакете". Вы должны быть в состоянии адаптировать его к вашим потребностям.

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: Ах, в этом случае вам может понадобиться и этот фрагмент (тот же вариант использования :))

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);
    }
}
3 голосов
/ 11 октября 2016

Я портировал ответ acheron55 на Java 7 и закрыл объект FileSystem. Этот код работает в IDE, в файлах jar и jar во время войны с Tomcat 7; но обратите внимание, что он не работает в банке во время войны с JBoss 7 (он дает FileSystemNotFoundException: Provider "vfs" not installed, см. также этот пост ) Кроме того, как и оригинальный код, он не является потокобезопасным, как это предлагает errr . По этим причинам я отказался от этого решения; однако, если вы можете принять эти вопросы, вот мой готовый код:

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;
                }
            });
        }
    }
}
3 голосов
/ 03 августа 2015

Вот пример использования библиотеки Reflections для рекурсивного сканирования пути к классам по шаблону имени регулярного выражения, дополненному парой Guava привилегий для получения содержимого ресурсов:

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

Работает как с банками, так и с взорванными классами.

3 голосов
/ 28 мая 2011

Некоторое время назад я сделал функцию, которая получает класс изнутри 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;
}
3 голосов
/ 15 сентября 2009

Учитывая фактический файл JAR, вы можете перечислить содержимое, используя JarFile.entries(). Вам нужно будет знать местоположение файла JAR - вы не можете просто попросить загрузчик классов перечислить все, что он может получить.

Вы должны быть в состоянии определить местоположение файла JAR, основываясь на URL-адресе, возвращенном с ThisClassName.class.getResource("ThisClassName.class"), но это может быть немного сложновато.

...