Bagaimana cara membuat daftar file di dalam file JAR?

114

Saya memiliki kode ini yang membaca semua file dari direktori.

    File textFolder = new File("text_directory");

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

Ini bekerja dengan baik. Ini mengisi array dengan semua file yang diakhiri dengan ".txt" dari direktori "text_directory".

Bagaimana saya bisa membaca isi direktori dengan cara yang sama di dalam file JAR?

Jadi yang benar-benar ingin saya lakukan adalah, mencantumkan semua gambar di dalam file JAR saya, sehingga saya dapat memuatnya dengan:

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

(Yang itu berfungsi karena "CompanyLogo" adalah "hardcode" tetapi jumlah gambar di dalam file JAR bisa dari 10 hingga 200 panjang variabel.)

EDIT

Jadi saya kira masalah utama saya adalah: Bagaimana cara mengetahui nama file JAR dimana kelas utama saya tinggal?

Memang saya bisa membacanya menggunakan java.util.Zip.

Struktur saya seperti ini:

Mereka seperti:

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 

Sekarang saya bisa memuat misalnya "images / image01.png" menggunakan:

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

Tapi hanya karena saya tahu nama filenya, selebihnya saya harus memuatnya secara dinamis.

OscarRyz
sumber
Hanya pemikiran - mengapa tidak gambar zip / jar ke dalam file terpisah dan membaca entri di dalamnya dari kelas Anda di toples lain?
Vineet Reynolds
3
Karena akan membutuhkan langkah "ekstra" untuk distribusi / instalasi. :( Anda tahu, pengguna akhir.
OscarRyz
Mengingat bahwa Anda telah membuat toples, Anda sebaiknya menyertakan daftar file di dalamnya daripada mencoba trik apa pun.
Tom Hawtin - tackline
Yah, saya mungkin salah tapi toples bisa tertanam di dalam toples lain. Solusi pengemasan satu botol (TM) ibm.com/developerworks/java/library/j-onejar bekerja atas dasar ini. Kecuali, dalam kasus Anda, Anda tidak memerlukan kelas beban kemampuan.
Vineet Reynolds

Jawaban:

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

Perhatikan bahwa di Java 7, Anda dapat membuat FileSystemdari file JAR (zip), lalu menggunakan mekanisme berjalan dan pemfilteran direktori NIO untuk mencarinya. Ini akan mempermudah penulisan kode yang menangani JAR dan direktori "meledak".

erickson
sumber
hei terima kasih ... sudah mencari cara untuk melakukan ini selama beberapa jam sekarang !!
Newtopian
9
Ya, kode ini berfungsi jika kita ingin mencantumkan semua entri di dalam file jar ini. Tetapi jika saya hanya ingin membuat daftar subdirektori di dalam jar, misalnya, example.jar / dir1 / dir2 / , bagaimana saya bisa langsung mendaftar semua file di dalam subdirektori ini? Atau saya perlu mengekstrak file jar ini? Saya sangat menghargai bantuan Anda!
Ensom Hodder
Pendekatan Java 7 yang disebutkan tercantum dalam jawaban @ acheron55 .
Vadzim
@Vadzim Apakah Anda yakin jawaban acheron55 untuk Java 7? Saya tidak menemukan Files.walk () atau java.util.Stream di Java 7, tetapi di Java 8: docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html
Bruce Minggu
@BruceSun, di java 7 Anda dapat menggunakan Files.walkFileTree (...) sebagai gantinya.
Vadzim
80

Kode yang berfungsi untuk file IDE dan .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());
        }
    }
}
acheron55
sumber
5
FileSystems.newFileSystem()mengambil Map<String, ?>, jadi Anda perlu menentukan untuk Collections.emptyMap()itu perlu mengembalikan yang diketik sesuai. Ini bekerja: Collections.<String, Object>emptyMap().
Zero3
6
Fantastis!!! tapi URI uri = MyClass.class.getResource ("/ resources"). toURI (); harus memiliki MyClass.class.getClassLoader (). getResource ("/ resources"). toURI (); yaitu, getClassLoader (). Jika tidak, itu tidak akan berhasil untuk saya.
EMM
8
Jangan lupa tutup fileSystem!
gmjonker
3
Ini harus menjadi jawaban pertama untuk 1.8 ( walkmetode Fileshanya tersedia di 1.8). Satu-satunya masalah adalah direktori sumber daya muncul di Files.walk(myPath, 1), bukan hanya di file. Saya kira elemen pertama dapat diabaikan begitu saja
toto_tico
4
myPath = fileSystem.getPath("/resources");tidak bekerja untuk saya; itu tidak menemukan apa pun. Seharusnya "gambar" dalam kasus saya dan "gambar" -direktori sudah pasti termasuk dalam toples saya!
phip1611
21

jawaban erickson bekerja dengan sempurna:

Inilah kode kerja.

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

Dan saya baru saja mengubah metode pemuatan saya dari ini:

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

Untuk ini:

String  [] webimages = ...

BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));
OscarRyz
sumber
9

Saya ingin memperluas jawaban acheron55 , karena ini adalah solusi yang sangat tidak aman, karena beberapa alasan:

  1. Itu tidak menutup FileSystemobjek.
  2. Itu tidak memeriksa apakah FileSystemobjek sudah ada.
  3. Ini tidak aman untuk benang.

Ini adalah solusi yang lebih aman:

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

Tidak perlu melakukan sinkronisasi melalui nama file; seseorang dapat dengan mudah melakukan sinkronisasi pada objek yang sama setiap saat (atau membuat metodesynchronized ), ini murni pengoptimalan.

Saya akan mengatakan bahwa ini masih merupakan solusi yang bermasalah, karena mungkin ada bagian lain dalam kode yang menggunakan FileSystemantarmuka di atas file yang sama, dan itu dapat mengganggu mereka (bahkan dalam satu aplikasi berulir).
Juga, itu tidak memeriksa nulls (misalnya, padagetClass().getResource() .

Antarmuka Java NIO khusus ini agak mengerikan, karena ia memperkenalkan sumber daya non-thread-safe global / tunggal, dan dokumentasinya sangat kabur (banyak yang tidak diketahui karena implementasi khusus penyedia). Hasil dapat berbeda untuk FileSystempenyedia lain (bukan JAR). Mungkin ada alasan bagus untuk itu; Saya tidak tahu, saya belum meneliti implementasinya.

Eyal Roth
sumber
1
Sinkronisasi sumber daya eksternal, seperti FS, tidak memiliki banyak arti dalam satu VM. Mungkin ada aplikasi lain yang mengaksesnya di luar VM Anda. Selain bahkan di dalam aplikasi Anda sendiri, penguncian Anda berdasarkan nama file dapat dengan mudah dilewati. Dengan hal ini lebih baik mengandalkan mekanisme sinkronisasi OS, seperti penguncian file.
Espinosa
@Espinosa Mekanisme kunci nama file benar-benar dapat dilewati; jawaban saya tidak cukup aman juga, tapi saya yakin itu yang paling bisa Anda dapatkan dengan Java NIO dengan sedikit usaha. Mengandalkan OS untuk mengelola kunci, atau tidak mengontrol aplikasi mana yang mengakses file mana merupakan praktik yang buruk IMHO, kecuali Anda sedang membangun aplikasi berbasis pelanggan - katakanlah, editor teks. Tidak mengelola kunci sendiri akan menyebabkan pengecualian terlempar, atau menyebabkan utas memblokir aplikasi - keduanya harus dihindari.
Eyal Roth
8

Jadi saya kira masalah utama saya adalah, bagaimana mengetahui nama toples tempat kelas utama saya tinggal.

Dengan asumsi bahwa proyek Anda dikemas dalam Jar (tidak harus benar!), Anda dapat menggunakan ClassLoader.getResource () atau findResource () dengan nama kelas (diikuti oleh .class) untuk mendapatkan jar yang berisi kelas tertentu. Anda harus mengurai nama jar dari URL yang dikembalikan (tidak terlalu sulit), yang akan saya tinggalkan sebagai latihan untuk pembaca :-)

Pastikan untuk menguji kasus yang kelasnya bukan bagian dari toples.

Kevin Day
sumber
1
huh - menarik bahwa ini akan di-down mod tanpa komentar ... Kami menggunakan teknik di atas sepanjang waktu dan bekerja dengan baik.
Kevin Day
Masalah lama, tetapi bagi saya ini sepertinya peretasan yang bagus. Suara positif kembali ke nol :)
Tuukka Mustonen
Suara positif, karena ini adalah satu-satunya solusi yang tercantum di sini untuk kasus ketika kelas tidak memiliki file CodeSource.
Kembalikan Monica 2331977
7

Saya telah mentransfer jawaban acheron55 ke Java 7 dan menutup FileSystemobjek. Kode ini bekerja di IDE, di file jar dan di jar di dalam perang melawan Tomcat 7; tetapi perhatikan bahwa itu tidak berfungsi di stoples di dalam perang terhadap JBoss 7 (itu memberi FileSystemNotFoundException: Provider "vfs" not installed, lihat juga posting ini ). Selain itu, seperti kode aslinya, ini bukan thread safe, seperti yang disarankan oleh errr . Untuk alasan ini saya telah meninggalkan solusi ini; namun, jika Anda dapat menerima masalah ini, berikut adalah kode siap pakai saya:

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;
                }
            });
        }
    }
}
Pino
sumber
5

Berikut adalah metode yang saya tulis untuk "menjalankan semua JUnits di bawah paket". Anda harus bisa menyesuaikannya dengan kebutuhan Anda.

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, dalam hal ini, Anda mungkin menginginkan cuplikan ini juga (kasus penggunaan yang sama :))

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);
    }
}
Ran Biron
sumber
1
Saya kira masalah besarnya adalah mengetahui nama file jar di tempat pertama. Ini adalah toples tempat Kelas-Utama: tinggal.
OscarRyz
5

Berikut adalah contoh penggunaan pustaka Refleksi untuk memindai jalur kelas secara rekursif dengan pola nama regex yang ditambah dengan beberapa fasilitas Guava untuk mengambil konten sumber daya:

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

Ini bekerja dengan toples dan kelas yang meledak.

Vadzim
sumber
Berhati-hatilah karena refleksi masih belum mendukung Java 9 dan yang lebih baru: github.com/ronmamo/reflections/issues/186 . Ada tautan ke perpustakaan yang bersaing di sana.
Vadzim
3

File jar hanyalah file zip dengan manifes terstruktur. Anda dapat membuka file jar dengan alat java zip biasa dan memindai isi file dengan cara itu, memompa aliran, dll. Kemudian menggunakannya dalam panggilan getResourceAsStream, dan itu harus semua keren dory.

EDIT / setelah klarifikasi

Saya butuh satu menit untuk mengingat semua bagian dan saya yakin ada cara yang lebih bersih untuk melakukannya, tetapi saya ingin melihat bahwa saya tidak gila. Dalam proyek saya image.jpg adalah file di beberapa bagian dari file jar utama. Saya mendapatkan pemuat kelas dari kelas utama (SomeClass adalah titik masuk) dan menggunakannya untuk menemukan sumber daya image.jpg. Kemudian beberapa keajaiban aliran untuk memasukkannya ke dalam hal ImageInputStream ini dan semuanya baik-baik saja.

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
Mikeb
sumber
Guci ini berisi program utama dan sumber daya. Bagaimana cara merujuk ke stoples mandiri? dari dalam file jar?
OscarRyz
Untuk merujuk ke file JAR, cukup gunakan "blah.JAR" sebagai String. Anda dapat menggunakan new File("blah.JAR")untuk membuat objek File yang mewakili JAR, misalnya. Cukup ganti "blah.JAR" dengan nama JAR Anda.
Thomas Owens
Jika toples yang sama yang Anda sudah kehabisan, pemuat kelas seharusnya dapat melihat barang-barang di dalam toples ... Saya salah paham apa yang Anda coba lakukan pada awalnya.
Mikeb
2
Ya, saya sudah punya itu, masalahnya adalah ketika saya membutuhkan sesuatu seperti: "... getResourceAsStream (" *. Jpg "); ..." Artinya, secara dinamis, daftar file yang ada.
OscarRyz
3

Diberikan file JAR yang sebenarnya, Anda dapat membuat daftar isinya menggunakan JarFile.entries(). Anda perlu mengetahui lokasi file JAR - Anda tidak bisa hanya meminta classloader untuk mencantumkan semua yang bisa didapatnya.

Anda harus dapat menentukan lokasi file JAR berdasarkan URL yang dikembalikan dari ThisClassName.class.getResource("ThisClassName.class"), tetapi ini mungkin sedikit rumit.

Jon Skeet
sumber
Membaca jawaban Anda, pertanyaan lain muncul. Apa yang akan menghasilkan panggilan: this.getClass (). GetResource ("/ my_directory"); Ini harus mengembalikan URL yang pada gilirannya bisa .... digunakan sebagai direktori? Nahh ... biarkan aku mencobanya.
OscarRyz
Anda selalu tahu lokasi JAR - ada di ".". Selama nama JAR diketahui sesuatu, Anda dapat menggunakan konstanta String di suatu tempat. Sekarang, jika orang pergi mengubah nama JAR ...
Thomas Owens
@ Thomas: Itu dengan asumsi Anda menjalankan aplikasi dari direktori saat ini. Apa yang salah dengan "java -jar foo / bar / baz.jar"?
Jon Skeet
Saya percaya (dan harus memverifikasi), bahwa jika Anda memiliki kode di Jar Anda new File("baz.jar), objek File akan mewakili file JAR Anda.
Thomas Owens
@ Thomas: Saya tidak percaya begitu. Saya yakin itu akan relatif terhadap direktori kerja saat ini dari proses tersebut. Saya harus memeriksanya juga :)
Jon Skeet
3

Beberapa waktu lalu saya membuat sebuah fungsi yang mendapat classess dari dalam 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;
}
berni
sumber
2
public static ArrayList<String> listItems(String path) throws Exception{
    InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(path);
    byte[] b = new byte[in.available()];
    in.read(b);
    String data = new String(b);
    String[] s = data.split("\n");
    List<String> a = Arrays.asList(s);
    ArrayList<String> m = new ArrayList<>(a);
    return m;
}
Felix G.
sumber
3
Meskipun cuplikan kode ini dapat menyelesaikan masalah, cuplikan kode ini tidak menjelaskan mengapa atau bagaimana menjawab pertanyaan tersebut. Harap sertakan penjelasan untuk kode Anda , karena itu sangat membantu meningkatkan kualitas posting Anda. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa mendatang, dan orang-orang itu mungkin tidak tahu alasan saran kode Anda.
Samuel Philipp
data kosong saat kita menjalankan kode dari file jar.
Aguid
1

Mekanisme paling kuat untuk mencantumkan semua resource di classpath saat ini menggunakan pola ini dengan ClassGraph , karena pola ini menangani larik mekanisme spesifikasi classpath seluas mungkin , termasuk sistem modul JPMS yang baru. (Saya adalah penulis ClassGraph.)

Bagaimana cara mengetahui nama file JAR tempat kelas utama saya tinggal?

URI mainClasspathElementURI;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
        .enableClassInfo().scan()) {
    mainClasspathElementURI =
            scanResult.getClassInfo("x.y.z.MainClass").getClasspathElementURI();
}

Bagaimana saya bisa membaca isi direktori dengan cara yang sama di dalam file JAR?

List<String> classpathElementResourcePaths;
try (ScanResult scanResult = new ClassGraph().overrideClasspath(mainClasspathElementURI)
        .scan()) {
    classpathElementResourcePaths = scanResult.getAllResources().getPaths();
}

Ada banyak cara lain untuk menangani sumber daya juga.

Luke Hutchison
sumber
1
Paket yang sangat bagus, mudah digunakan dalam proyek Scala saya, terima kasih.
zslim
0

Hanya cara yang berbeda untuk mendaftar / membaca file dari URL jar dan melakukannya secara rekursif untuk toples bersarang

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 
    }
});
trung
sumber
0

Satu lagi untuk jalan:

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;

import static java.nio.file.FileSystems.newFileSystem;
import static java.util.Collections.emptyMap;

public class ResourceWalker {
  private static final PathMatcher FILE_MATCHER =
      FileSystems.getDefault().getPathMatcher( "glob:**.ttf" );

  public static List<Path> walk( final String directory )
      throws URISyntaxException, IOException {
    final List<Path> filenames = new ArrayList<>();
    final var resource = ResourceWalker.class.getResource( directory );

    if( resource != null ) {
      final var uri = resource.toURI();
      final var path = uri.getScheme().equals( "jar" )
          ? newFileSystem( uri, emptyMap() ).getPath( directory )
          : Paths.get( uri );
      final var walk = Files.walk( path, 10 );

      for( final var it = walk.iterator(); it.hasNext(); ) {
        final Path p = it.next();
        if( FILE_MATCHER.matches( p ) ) {
          filenames.add( p );
        }
      }
    }

    return filenames;
  }
}

Ini sedikit lebih fleksibel untuk mencocokkan nama file tertentu karena menggunakan penggabungan karakter pengganti.


Gaya yang lebih fungsional:

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.function.Consumer;

import static java.nio.file.FileSystems.newFileSystem;
import static java.util.Collections.emptyMap;

/**
 * Responsible for finding file resources.
 */
public class ResourceWalker {
  private static final PathMatcher FILE_MATCHER =
      FileSystems.getDefault().getPathMatcher( "glob:**.ttf" );

  public static void walk( final String dirName, final Consumer<Path> f )
      throws URISyntaxException, IOException {
    final var resource = ResourceWalker.class.getResource( dirName );

    if( resource != null ) {
      final var uri = resource.toURI();
      final var path = uri.getScheme().equals( "jar" )
          ? newFileSystem( uri, emptyMap() ).getPath( dirName )
          : Paths.get( uri );
      final var walk = Files.walk( path, 10 );

      for( final var it = walk.iterator(); it.hasNext(); ) {
        final Path p = it.next();
        if( FILE_MATCHER.matches( p ) ) {
          f.accept( p );
        }
      }
    }
  }
}
Dave Jarvis
sumber