Как перечислить файлы в JAR-файле?

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

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 переменной длины.)

РЕДАКТИРОВАТЬ

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

Конечно, я мог прочитать его с помощью 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)); 

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

 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 и «взорванные» каталоги.

Код, который работает для файлов 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.emptyMap()); myPath = fileSystem.getPath("/resources"); } else { myPath = Paths.get(uri); } Stream walk = Files.walk(myPath, 1); for (Iterator it = walk.iterator(); it.hasNext();){ System.out.println(it.next()); } } } 

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

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

 CodeSource src = MyClass.class.getProtectionDomain().getCodeSource(); List list = new ArrayList(); 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])); 

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

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

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

 private static ConcurrentMap 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.emptyMap()); } } 

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

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

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

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

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

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

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

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

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

Файл jar – это всего лишь zip-файл со структурированным манифестом. Вы можете открыть файл jar с помощью обычных инструментов java zip и отсканировать содержимое файла таким образом, раздуть streamи и т. Д. Затем используйте это в вызове getResourceAsStream, и это должно быть все hunky dory.

EDIT / после уточнения

Мне потребовалась минута, чтобы запомнить все кусочки и я уверен, что есть более чистые способы сделать это, но я хотел видеть, что я не сумасшедший. В моем проекте image.jpg – это файл в некоторой части основного файла jar. Я получаю загрузчик classов основного classа (SomeClass – это точка входа) и используйте его для обнаружения ресурса image.jpg. Тогда какая-то магия streamа, чтобы попасть в эту вещь ImageInputStream, и все в порядке.

 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 

Некоторое время назад я сделал функцию, которая становится classной изнутри JAR:

 public static Class[] getClasses(String packageName) throws ClassNotFoundException{ ArrayList classes = new ArrayList (); 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; } 

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

 Reflections reflections = new Reflections("com.example.package", new ResourcesScanner()); Set paths = reflections.getResources(Pattern.compile(".*\\.template$")); Map 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); } 

Это работает как с банками, так и с разнесенными classами.

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

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

Есть две очень полезные утилиты, называемые JarScan:

  1. http://www.inetfeedback.com/jarscan

  2. jarscan.dev.java.net

См. Также этот вопрос: JarScan, сканировать все файлы JAR во всех подпапках для определенного classа

Я портировал ответ acheron55 на Java 7 и закрыл объект FileSystem . Этот код работает в IDE, в файлах jar и в банке во время войны с Tomcat 7; но обратите внимание, что он не работает в банке во время войны с JBoss 7 (это дает FileSystemNotFoundException: Provider "vfs" not installed , см. также этот пост ). Кроме того, как и исходный код, он не является streamобезопасным, как было предложено 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.emptyMap()) : null)) { Path myPath = Paths.get(uri); Files.walkFileTree(myPath, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { System.out.println(file); return FileVisitResult.CONTINUE; } }); } } } 

Просто другой способ enums / чтения файлов из URL-адреса jar и он рекурсивно для вложенных банок

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 } }); 
Давайте будем гением компьютера.