2009-09-15 3 views
88

У меня есть этот код, который читает все файлы из каталога.Как перечислить файлы внутри 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 переменной длины)

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 

Прямо сейчас я могу загрузить, например, "картинки/image01.png" с помощью:

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

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

+0

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

+2

Потому что для распределения/установки потребуется дополнительный шаг. :(Знаете, конечные пользователи. – OscarRyz

+0

Ну, я могу ошибаться, но банки могут быть встроены в другие банки. Однобарабанное (TM) упаковочное решение http://www.ibm.com/developerworks/java/library/j -onejar/работает на этой основе.Кроме того, в вашем случае вам не требуются классы нагрузки. –

ответ

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

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

+0

hey thanks ... искал способ сделать это в течение нескольких часов! – Newtopian

+8

Да, этот код работает, если мы хотим перечислить все записи внутри этого файла jar. Но если я просто хочу перечислите подкаталог внутри jar, например, ** example.jar/dir1/dir2/**, как я могу напрямую перечислить все файлы внутри этого подкаталога? Или мне нужно разархивировать этот файл jar? Я очень ценю вашу помощь! –

+0

См. Также http://stackoverflow.com/a/5194002/603516 – Vadzim

3

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

EDIT/после осветления

Потребовалось мне минуту, чтобы помнить все биты и куски, и я уверен, что есть более чистые способы сделать это, но я хотел бы видеть, что я не сошел с ума. В моем проекте image.jpg - это файл в некоторой части основного файла jar. Я получаю загрузчик классов основного класса (SomeClass - это точка входа) и используйте его для обнаружения ресурса image.jpg. Тогда какая-то магия потока, чтобы попасть в эту вещь 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 
+0

Эта банку содержит основную программу и ресурсы. Как я могу ссылаться на самофинансирование? из файла jar? – OscarRyz

+0

Чтобы обратиться к файлу JAR, просто используйте «blah.JAR» в качестве строки. Вы можете использовать 'new File (« blah.JAR »)' для создания объекта File, который представляет JAR, например. Просто замените «blah.JAR» на имя вашего JAR. –

+0

Если в той же самой банке, что вы уже закончили, загрузчик классов должен иметь возможность видеть вещи внутри банки ... Я неправильно понял, что вы пытались сделать изначально. – Mikeb

2

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

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

+0

Чтение вашего ответа на другой поднятый вопрос. Что даст вызов: this.getClass(). GetResource ("/ my_directory"); Он должен вернуть URL-адрес, который, в свою очередь, может быть ... использован как каталог? Нахх ... позволь мне попробовать. – OscarRyz

+0

Вы всегда знаете расположение JAR - оно находится в ".". Пока имя JAR известно как нечто, вы можете где-то использовать константу String. Теперь, если люди меняют имя JAR ... –

+0

@Thomas: Предполагается, что вы запускаете приложение из текущего каталога. Что случилось с «java -jar foo/bar/baz.jar»? –

4

Вот метод, который я написал для «запускать все 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); 
    } 
} 
+1

Я полагаю, что большая проблема заключается в том, чтобы знать имя файла jar на первом месте. Это банку, где Main-Class: lives. – OscarRyz

5

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

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

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

+0

huh - интересно, что это было бы без изменений, без комментариев ... Мы используем вышеуказанную технику все время, и все работает отлично. –

+0

Старая проблема, но для меня это кажется прекрасным взломом. Upvoted back to zero :) –

+0

Upvoted, потому что это единственное решение, перечисленное здесь для случая, когда класс не имеет «CodeSource». – studro

18

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

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

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

Некоторое время назад я сделал функцию, которая получает classess внутри 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

код, который работает на как IDE, так и.баночка файлы:

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()' принимает 'Map ', поэтому вам нужно указать 'Collections.emptyMap()', что ему нужно вернуть подходящий тип. Это работает: 'Коллекции. emptyMap()'. – Zero3

+4

Фантастический !!! но URI uri = MyClass.class.getResource ("/ resources"). toURI(); должен иметь MyClass.class.getClassLoader(). getResource ("/ resources"). toURI(); то есть getClassLoader(). В противном случае это не работало для меня. – EMM

+4

Не забудьте закрыть 'fileSystem'! – gmjonker

0

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

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

Вот пример использования 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); 
} 

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

5

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

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

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

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

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

+0

Синхронизация внешних ресурсов, таких как FS, не имеет большого смысла внутри одной виртуальной машины. Доступ к другим приложениям может быть за пределами вашей виртуальной машины. Кроме того, даже внутри вашего собственного приложения ваша блокировка на основе имен файлов может быть легко обойдена. При этом лучше полагаться на механизмы синхронизации ОС, такие как блокировка файлов. – Espinosa

+0

@ Espinosa Механизм блокировки имени файла может быть полностью обойден; мой ответ тоже небезопасен, но я считаю, что это самое лучшее, что вы можете получить с помощью Java NIO с минимальными усилиями. Опираясь на ОС для управления блокировками или не контролируя, какие приложения получают доступ к тем файлам, которые являются плохой практикой IMHO, если вы не создаете приложение на основе калькулятора, скажем, текстовый редактор. Не управлять собственными блокировками может привести либо к тому, что будут выбрасываться исключения, либо заставить потоки блокировать приложение - оба следует избегать. –

0

Я портировал acheron55's answer на Java 7 и закрыл объект FileSystem. Этот код работает в файлах IDE, в файлах jar и в банке во время войны с Tomcat 7, но не е, что он не работает в банке внутри войны на JBoss 7 (он дает FileSystemNotFoundException: Provider "vfs" not installed, см. также this post). Кроме того, как и исходный код, он не является потокобезопасным, как предложено 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; 
       } 
      }); 
     } 
    } 
}