Como listar os arquivos dentro de um arquivo JAR?
-
07-07-2019 - |
Pergunta
Eu tenho esse código que lê todos os arquivos de um diretório.
File textFolder = new File("text_directory");
File [] texFiles = textFolder.listFiles( new FileFilter() {
public boolean accept( File file ) {
return file.getName().endsWith(".txt");
}
});
Ele funciona muito bem. Ele preenche a matriz com todos os arquivos que terminam com ".txt" do diretório 'text_directory'.
Como posso ler o conteúdo de um diretório em uma forma similar dentro um arquivo JAR?
Então, o que eu realmente quero fazer é, para listar todas as imagens dentro do meu arquivo JAR, então eu posso carregá-los com:
ImageIO.read(this.getClass().getResource("CompanyLogo.png"));
(aquele obras porque o "LogoCompanhia" é "codificado", mas o número de imagens dentro do arquivo JAR pode ser de 10 a 200 de comprimento variável.)
Editar
Então eu acho que o meu principal problema seria:? Como saber o nome do arquivo JAR , onde minhas principais vidas de classe
Concedido eu poderia lê-lo usando java.util.Zip
.
Meu Estrutura é assim:
Eles são como:
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
Agora eu sou capaz de carregar, por exemplo, "images / image01.png" usando:
ImageIO.read(this.getClass().getResource("images/image01.png));
Mas só porque eu sei o nome do arquivo, para o resto eu tenho que carregá-los dinamicamente.
Solução
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... */
}
Note que em Java 7, você pode criar um FileSystem
do arquivo JAR (zip), e depois usar curta diretório do NIO e filtrar mecanismos para procurar por ele. Isso tornaria mais fácil de escrever código que lida com JARs e "explodiu" diretórios.
Outras dicas
código que funcione para ambos do IDE e .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());
}
}
}
de Erickson funcionou perfeitamente:
Aqui está o código de trabalho.
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 acabo de modificar o meu método de carga a partir deste:
File[] webimages = ...
BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex].getName() ));
Para isto:
String [] webimages = ...
BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));
Eu gostaria de expandir em acheron55 de resposta , uma vez que é uma solução muito não-seguro, por várias razões:
- Ele não fecha o objeto
FileSystem
. - Não verificar se o objeto
FileSystem
já existe. - Não é thread-safe.
Este é um pouco uma solução mais segura:
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());
}
}
Não há nenhuma necessidade real para sincronizar sobre o nome do arquivo; pode-se simplesmente sincronizar no mesmo objeto de cada vez (ou fazer a synchronized
método), é puramente uma otimização.
Eu diria que esta é ainda uma solução problemática, uma vez que pode haver outras partes do código que usa a interface FileSystem
sobre os mesmos arquivos, e isso poderia interferir com eles (mesmo em uma única aplicação de rosca).
Além disso, ele não verifica null
s (por exemplo, em getClass().getResource()
.
Esta interface Java NIO especial é uma espécie de horrível, uma vez que introduz um / global Singleton recurso não thread-safe, e sua documentação é extremamente vaga (muitas incógnitas devido a implementações de provedor específico). Os resultados podem variar para outros provedores FileSystem
(não JAR). Talvez haja uma boa razão para que seja assim; Eu não sei, eu não pesquisei as implementações.
Aqui está um método que escrevi para a "executar todos JUnits sob um pacote". Você deve ser capaz de adaptá-lo às suas necessidades.
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, nesse caso, você pode querer esse trecho bem (mesmo caso de 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);
}
}
Então eu acho que o meu principal problema seria, como saber o nome do frasco onde meus principais vidas de classe.
Assumindo que o seu projecto é embalado em um frasco (não necessariamente verdade!), Você pode usar ClassLoader.getResource () ou FindResource () com o nome da classe (seguido de .class) para obter o frasco que contém uma determinada classe . Você vai ter que analisar o nome do frasco do URL que será devolvido (não tão difícil), que vou deixar como um exercício para o leitor: -)
Certifique-se de teste para o caso em que a classe não faz parte de um copo.
Um arquivo jar é apenas um arquivo zip com um manifesto estruturado. Você pode abrir o arquivo jar com as ferramentas habituais java zip e digitalizar o conteúdo do arquivo dessa forma, os fluxos de inflar, etc. Em seguida, usar isso em uma chamada getResourceAsStream, e deve ser tudo Hunky Dory.
EDIT / após clarificação
Levei um minuto para lembrar de todos os pedaços e eu tenho certeza que existem formas mais limpas de fazer isso, mas eu queria ver que eu não era louco. Na minha image.jpg projeto é um arquivo em alguma parte do arquivo jar principal. Recebo o carregador de classe da classe principal (SomeClass é o ponto de entrada) e usá-lo para descobrir o recurso image.jpg. Então alguma mágica fluxo para obtê-lo nessa coisa ImageInputStream e está tudo bem.
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
Dado um arquivo JAR real, você pode listar o conteúdo usando JarFile.entries()
. Você precisa saber a localização do arquivo JAR embora -. Você não pode simplesmente pedir o carregador de classe à lista tudo o que poderia obter pelo
Você deve ser capaz de trabalhar fora do local do arquivo JAR com base na URL retornou de ThisClassName.class.getResource("ThisClassName.class")
, mas pode ser um pouco complicadas.
Algum tempo atrás eu fiz uma função que recebe classess de JAR dentro:
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;
}
Aqui está um exemplo do uso Reflexões biblioteca para classpath recursivamente varredura por padrão de nome regex aumentada com um par de Goiaba regalias para buscar conteúdo de recursos:
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);
}
Isso funciona com ambos os frascos e explodiu classes.
Eu tenho portado acheron55 é responder para Java 7 e fechou o objeto FileSystem
. Esse código funciona no IDE do, em arquivos jar e em um frasco dentro de uma guerra contra o Tomcat 7; Mas note que ele faz não trabalho em um frasco dentro de uma guerra contra o JBoss 7 (dá FileSystemNotFoundException: Provider "vfs" not installed
, ver também este post ). Além disso, como o código original, não é thread-safe, como sugerido por errr . Por estas razões, abandonaram esta solução; No entanto, se você pode aceitar estas questões, aqui está o meu código 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;
}
});
}
}
}
Existem dois utilitários muito úteis tanto chamados JarScan:
Veja também esta pergunta: JarScan, verificar todos os arquivos JAR em todas as subpastas para classe específica
Apenas uma maneira diferente de listar / leitura de arquivos a partir de uma URL frasco e ele faz isso de forma recursiva para frascos aninhados
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
}
});