Dapatkan daftar sumber daya dari direktori classpath

247

Saya mencari cara untuk mendapatkan daftar semua nama sumber daya dari direktori classpath yang diberikan, sesuatu seperti metode List<String> getResourceNames (String directoryName) .

Sebagai contoh, diberikan sebuah direktori classpath x/y/zyang berisi file a.html, b.html, c.htmldan subdirektori d, getResourceNames("x/y/z")harus kembali List<String>mengandung string berikut:['a.html', 'b.html', 'c.html', 'd'] .

Ini harus bekerja baik untuk sumber daya dalam sistem file dan stoples.

Saya tahu bahwa saya dapat menulis cuplikan singkat dengan Files, JarFiles dan URLs, tetapi saya tidak ingin menemukan kembali kemudi. Pertanyaan saya adalah, mengingat perpustakaan yang ada tersedia untuk umum, apa cara tercepat untuk mengimplementasikan getResourceNames? Tumpukan Spring dan Apache Commons keduanya layak.

viaclectic
sumber
1
Jawaban terkait: stackoverflow.com/a/30149061/4102160
Cfx
Periksa proyek ini, selesaikan
Felix Aballi

Jawaban:

165

Pemindai Khusus

Terapkan pemindai Anda sendiri. Sebagai contoh:

private List<String> getResourceFiles(String path) throws IOException {
    List<String> filenames = new ArrayList<>();

    try (
            InputStream in = getResourceAsStream(path);
            BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
        String resource;

        while ((resource = br.readLine()) != null) {
            filenames.add(resource);
        }
    }

    return filenames;
}

private InputStream getResourceAsStream(String resource) {
    final InputStream in
            = getContextClassLoader().getResourceAsStream(resource);

    return in == null ? getClass().getResourceAsStream(resource) : in;
}

private ClassLoader getContextClassLoader() {
    return Thread.currentThread().getContextClassLoader();
}

Kerangka Pegas

Gunakan PathMatchingResourcePatternResolverdari Spring Framework.

Refleksi Ronmamo

Teknik lain mungkin lambat saat runtime untuk nilai CLASSPATH besar. Solusi yang lebih cepat adalah dengan menggunakan Reflection API ronmamo , yang mengompilasi pencarian pada waktu kompilasi.

iirekm
sumber
12
jika menggunakan Refleksi, semua yang Anda perlukan:new Reflections("my.package", new ResourcesScanner()).getResources(pattern)
zapp
27
Apakah solusi pertama berfungsi ketika dijalankan dari file jar? Bagi saya - tidak. Saya dapat membaca file dengan cara ini, tetapi tidak dapat membaca direktori.
Valentina Chumak
5
Metode pertama yang dijelaskan dalam jawaban ini - membaca jalur sebagai sumber daya, tampaknya tidak berfungsi ketika sumber daya berada dalam JAR yang sama dengan kode yang dapat dieksekusi, setidaknya dengan OpenJDK 1.8. Kegagalannya agak aneh - NullPointerException dilempar jauh dari dalam logika pemrosesan file JVM. Seolah-olah para desainer tidak benar-benar mengantisipasi penggunaan sumber daya ini, dan hanya ada implementasi parsial. Bahkan jika itu berfungsi pada beberapa JDK atau lingkungan, ini sepertinya bukan perilaku yang terdokumentasi.
Kevin Boone
7
Anda tidak dapat membaca direktori seperti ini, karena tidak ada direktori selain file stream. Jawaban ini setengah salah.
Thomas Decaux
4
Metode pertama tampaknya tidak berfungsi - setidaknya ketika menjalankan dari lingkungan pengembangan, sebelum menggelegak sumber daya. Panggilan getResourceAsStream()dengan jalur relatif ke direktori mengarah ke FileInputStream (File), yang didefinisikan untuk melempar FileNotFoundException jika file tersebut adalah direktori.
Andy Thomas
52

Berikut adalah kode
Sumber : forums.devx.com/showthread.php?t=153784

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

/**
 * list resources available from the classpath @ *
 */
public class ResourceList{

    /**
     * for all elements of java.class.path get a Collection of resources Pattern
     * pattern = Pattern.compile(".*"); gets all resources
     * 
     * @param pattern
     *            the pattern to match
     * @return the resources in the order they are found
     */
    public static Collection<String> getResources(
        final Pattern pattern){
        final ArrayList<String> retval = new ArrayList<String>();
        final String classPath = System.getProperty("java.class.path", ".");
        final String[] classPathElements = classPath.split(System.getProperty("path.separator"));
        for(final String element : classPathElements){
            retval.addAll(getResources(element, pattern));
        }
        return retval;
    }

    private static Collection<String> getResources(
        final String element,
        final Pattern pattern){
        final ArrayList<String> retval = new ArrayList<String>();
        final File file = new File(element);
        if(file.isDirectory()){
            retval.addAll(getResourcesFromDirectory(file, pattern));
        } else{
            retval.addAll(getResourcesFromJarFile(file, pattern));
        }
        return retval;
    }

    private static Collection<String> getResourcesFromJarFile(
        final File file,
        final Pattern pattern){
        final ArrayList<String> retval = new ArrayList<String>();
        ZipFile zf;
        try{
            zf = new ZipFile(file);
        } catch(final ZipException e){
            throw new Error(e);
        } catch(final IOException e){
            throw new Error(e);
        }
        final Enumeration e = zf.entries();
        while(e.hasMoreElements()){
            final ZipEntry ze = (ZipEntry) e.nextElement();
            final String fileName = ze.getName();
            final boolean accept = pattern.matcher(fileName).matches();
            if(accept){
                retval.add(fileName);
            }
        }
        try{
            zf.close();
        } catch(final IOException e1){
            throw new Error(e1);
        }
        return retval;
    }

    private static Collection<String> getResourcesFromDirectory(
        final File directory,
        final Pattern pattern){
        final ArrayList<String> retval = new ArrayList<String>();
        final File[] fileList = directory.listFiles();
        for(final File file : fileList){
            if(file.isDirectory()){
                retval.addAll(getResourcesFromDirectory(file, pattern));
            } else{
                try{
                    final String fileName = file.getCanonicalPath();
                    final boolean accept = pattern.matcher(fileName).matches();
                    if(accept){
                        retval.add(fileName);
                    }
                } catch(final IOException e){
                    throw new Error(e);
                }
            }
        }
        return retval;
    }

    /**
     * list the resources that match args[0]
     * 
     * @param args
     *            args[0] is the pattern to match, or list all resources if
     *            there are no args
     */
    public static void main(final String[] args){
        Pattern pattern;
        if(args.length < 1){
            pattern = Pattern.compile(".*");
        } else{
            pattern = Pattern.compile(args[0]);
        }
        final Collection<String> list = ResourceList.getResources(pattern);
        for(final String name : list){
            System.out.println(name);
        }
    }
}  

Jika Anda menggunakan Spring Lihatlah PathMatchingResourcePatternResolver

Jigar Joshi
sumber
3
Penanya tahu bagaimana menerapkannya menggunakan kelas java standar, tetapi ia ingin tahu bagaimana ia dapat memanfaatkan pustaka (Spring, Apache Commons) yang ada.
Jeroen Rosenberg
@ Joen Rosenberg Ada juga cara lain yang telah dipilih akhirnya :)
Jigar Joshi
7
Saya menemukan solusi ini berguna untuk kasus-kasus yang tidak Anda gunakan Spring - itu akan konyol untuk menambahkan ketergantungan pada Spring karena fitur ini saja. Jadi terima kasih "Kotor" tetapi bermanfaat :) PS One mungkin ingin menggunakan File.pathSeparatoralih-alih kode-keras: di getResourcesmetode.
Timur
5
Contoh kode adalah ketergantungan sistem, gunakan ini sebagai gantinya: final String[] classPathElements = classPath.split(System.pathSeparator);
dcompiled
1
Peringatan: Properti sistem java.class.path mungkin tidak mengandung classpath aktual yang Anda jalankan. Sebagai alternatif, Anda dapat melihat loader kelas dan menginterogasinya untuk apa URL itu memuat kelas. Peringatan lainnya tentu saja adalah bahwa jika Anda melakukan ini di bawah SecurityManager, konstruktor ZipFile dapat menolak akses Anda ke file, jadi Anda harus menangkapnya.
Trejkaz
24

Menggunakan Refleksi

Dapatkan semua yang ada di classpath:

Reflections reflections = new Reflections(null, new ResourcesScanner());
Set<String> resourceList = reflections.getResources(x -> true);

Contoh lain - dapatkan semua file dengan ekstensi .csv dari some.package :

Reflections reflections = new Reflections("some.package", new ResourcesScanner());
Set<String> fileNames = reflections.getResources(Pattern.compile(".*\\.csv"));
NS du Toit
sumber
apakah ada cara untuk mencapai hal yang sama tanpa harus mengimpor ketergantungan google? Saya ingin menghindari ketergantungan ini hanya untuk melakukan tugas ini.
Enrico Giurin
1
Refleksi bukan perpustakaan Google.
Luke Hutchison
Mungkin itu di masa lalu. Jawaban saya adalah dari tahun 2015 dan saya menemukan beberapa referensi ke perpustakaan menggunakan frase "Google Reflections" sebelum 2015. Saya melihat kode telah bergerak sekitar sedikit dan telah pindah dari code.google.com sini untuk github sini
NS du Toit
@NSduToit yang mungkin merupakan artefak dari hosting kode pada Google Code (bukan kesalahan yang tidak biasa), bukan klaim kepengarangan oleh Google.
matt b
17

Jika Anda menggunakan apache commonsIO, Anda dapat menggunakan sistem file (opsional dengan filter ekstensi):

Collection<File> files = FileUtils.listFiles(new File("directory/"), null, false);

dan untuk sumber daya / classpath:

List<String> files = IOUtils.readLines(MyClass.class.getClassLoader().getResourceAsStream("directory/"), Charsets.UTF_8);

Jika Anda tidak tahu apakah "directoy /" ada di sistem file atau sumber daya, Anda dapat menambahkan

if (new File("directory/").isDirectory())

atau

if (MyClass.class.getClassLoader().getResource("directory/") != null)

sebelum panggilan dan gunakan keduanya dalam kombinasi ...

rampok
sumber
23
Files! = Resources
djechlin
3
Tentu, sumber daya mungkin tidak selalu berupa file tetapi pertanyaannya adalah tentang sumber daya yang berasal dari file / direktori. Jadi, gunakan kode contoh jika Anda ingin memeriksa lokasi yang berbeda, mis. Jika Anda memiliki config.xml di sumber daya Anda untuk beberapa konfigurasi default dan seharusnya memungkinkan untuk memuat config.xml eksternal jika ada ...
Rob
2
Dan mengapa sumber daya bukan file? Sumber daya adalah file yang diarsipkan dalam arsip zip. Itu dimuat ke dalam memori dan diakses dengan cara tertentu tetapi saya tidak bisa melihat mengapa mereka bukan file. Adakah sumber daya yang bukan 'file di dalam arsip'?
Mike
10
Tidak berfungsi (NullPointerException) saat sumber daya target adalah direktori yang berada di dalam File JAR (yaitu, JAR berisi aplikasi Utama dan direktori target)
Carlitos Way
12

Jadi dalam hal PathMatchingResourcePatternResolver inilah yang diperlukan dalam kode:

@Autowired
ResourcePatternResolver resourceResolver;

public void getResources() {
  resourceResolver.getResources("classpath:config/*.xml");
}
Pavel Kotlov
sumber
hanya tambahan kecil jika Anda ingin mencari semua sumber daya yang mungkin di semua classpath yang perlu Anda gunakanclasspath*:config/*.xml
revau.lt
5

The Spring framework's PathMatchingResourcePatternResolverbenar-benar mengagumkan untuk hal-hal ini:

private Resource[] getXMLResources() throws IOException
{
    ClassLoader classLoader = MethodHandles.lookup().getClass().getClassLoader();
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);

    return resolver.getResources("classpath:x/y/z/*.xml");
}

Ketergantungan maven:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>LATEST</version>
</dependency>
BullyWiiPlaza
sumber
5

Menggunakan kombinasi respons Rob.

final String resourceDir = "resourceDirectory/";
List<String> files = IOUtils.readLines(Thread.currentThread().getClass().getClassLoader().getResourceAsStream(resourceDir), Charsets.UTF_8);

for(String f : files){
  String data= IOUtils.toString(Thread.currentThread().getClass().getClassLoader().getResourceAsStream(resourceDir + f));
  ....process data
}
Trevor
sumber
cara mendapatkan semua sumber daya: yaitu mulai dengan /' or .` atau ./tidak ada yang benar-benar berfungsi
javadba
3

Dengan Spring itu mudah. Baik itu file, folder, atau bahkan banyak file, ada kemungkinan, Anda bisa melakukannya melalui injeksi.

Contoh ini menunjukkan injeksi beberapa file yang terletak di x/y/zfolder.

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

@Service
public class StackoverflowService {
    @Value("classpath:x/y/z/*")
    private Resource[] resources;

    public List<String> getResourceNames() {
        return Arrays.stream(resources)
                .map(Resource::getFilename)
                .collect(Collectors.toList());
    }
}

Itu bekerja untuk sumber daya di sistem file maupun di JAR.

NaXa
sumber
2
Saya ingin mendapatkan nama folder di bawah / BOOT-INF / kelas / statis / cerita. Saya mencoba kode Anda dengan @Value ("classpath: static / stories / *") dan mengembalikan daftar kosong ketika menjalankan JAR. Ini berfungsi untuk sumber daya di classpath, tetapi tidak ketika mereka berada di JAR.
PS
@Value ("classpath: x / y / z / *") sumber daya pribadi [] sumber daya; melakukan trik padaku. Dapatkan jam pencarian untuk itu! Terima kasih!
Rudolf Schmidt
3

Ini harus berfungsi (jika pegas bukan opsi):

public static List<String> getFilenamesForDirnameFromCP(String directoryName) throws URISyntaxException, UnsupportedEncodingException, IOException {
    List<String> filenames = new ArrayList<>();

    URL url = Thread.currentThread().getContextClassLoader().getResource(directoryName);
    if (url != null) {
        if (url.getProtocol().equals("file")) {
            File file = Paths.get(url.toURI()).toFile();
            if (file != null) {
                File[] files = file.listFiles();
                if (files != null) {
                    for (File filename : files) {
                        filenames.add(filename.toString());
                    }
                }
            }
        } else if (url.getProtocol().equals("jar")) {
            String dirname = directoryName + "/";
            String path = url.getPath();
            String jarPath = path.substring(5, path.indexOf("!"));
            try (JarFile jar = new JarFile(URLDecoder.decode(jarPath, StandardCharsets.UTF_8.name()))) {
                Enumeration<JarEntry> entries = jar.entries();
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();
                    String name = entry.getName();
                    if (name.startsWith(dirname) && !dirname.equals(name)) {
                        URL resource = Thread.currentThread().getContextClassLoader().getResource(name);
                        filenames.add(resource.toString());
                    }
                }
            }
        }
    }
    return filenames;
}
fl0w
sumber
Ini menerima downvote baru-baru ini - jika Anda menemukan masalah dengannya, silakan bagikan, terima kasih!
fl0w
1
Terima kasih @ArnoUnkrig - silakan bagikan solusi Anda yang diperbarui jika Anda mau
fl0w
3

Cara saya, tanpa Spring, digunakan selama unit test:

URI uri = TestClass.class.getResource("/resources").toURI();
Path myPath = Paths.get(uri);
Stream<Path> walk = Files.walk(myPath, 1);
for (Iterator<Path> it = walk.iterator(); it.hasNext(); ) {
    Path filename = it.next();   
    System.out.println(filename);
}
Jacques Koort
sumber
tidak berfungsi, saat Anda menjalankan toples: java.nio.file.FileSystemNotFoundException di Paths.get (uri)
error1009
0

Saya pikir Anda dapat memanfaatkan [ Penyedia Sistem File Zip ] [1] untuk mencapai ini. Ketika menggunakanFileSystems.newFileSystem sepertinya Anda bisa memperlakukan objek dalam ZIP itu sebagai file "biasa".

Dalam dokumentasi tertaut di atas:

Tentukan opsi konfigurasi untuk sistem file zip di objek java.util.Map yang diteruskan ke FileSystems.newFileSystem metode. Lihat topik [Properti Sistem File Zip] [2] untuk informasi tentang properti konfigurasi khusus penyedia untuk sistem file zip.

Setelah Anda memiliki instance dari sistem file zip, Anda dapat memanggil metode dari kelas [ java.nio.file.FileSystem] [3] dan [ java.nio.file.Path] [4] untuk melakukan operasi seperti menyalin, memindahkan, dan mengganti nama file, serta memodifikasi atribut file.

Dokumentasi untuk jdk.zipfsmodul di [Jawa 11 negara] [5]:

Penyedia sistem file zip memperlakukan file zip atau JAR sebagai sistem file dan menyediakan kemampuan untuk memanipulasi konten file. Penyedia sistem file zip dapat dibuat oleh [ FileSystems.newFileSystem] [6] jika diinstal.

Ini adalah contoh yang dibuat-buat yang saya lakukan menggunakan sumber daya contoh Anda. Perhatikan bahwa a .zipadalah a.jar , tetapi Anda dapat mengadaptasi kode Anda untuk menggunakan sumber daya classpath:

Mempersiapkan

cd /tmp
mkdir -p x/y/z
touch x/y/z/{a,b,c}.html
echo 'hello world' > x/y/z/d
zip -r example.zip x

Jawa

import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.util.Collections;
import java.util.stream.Collectors;

public class MkobitZipRead {

  public static void main(String[] args) throws IOException {
    final URI uri = URI.create("jar:file:/tmp/example.zip");
    try (
        final FileSystem zipfs = FileSystems.newFileSystem(uri, Collections.emptyMap());
    ) {
      Files.walk(zipfs.getPath("/")).forEach(path -> System.out.println("Files in zip:" + path));
      System.out.println("-----");
      final String manifest = Files.readAllLines(
          zipfs.getPath("x", "y", "z").resolve("d")
      ).stream().collect(Collectors.joining(System.lineSeparator()));
      System.out.println(manifest);
    }
  }

}

Keluaran

Files in zip:/
Files in zip:/x/
Files in zip:/x/y/
Files in zip:/x/y/z/
Files in zip:/x/y/z/c.html
Files in zip:/x/y/z/b.html
Files in zip:/x/y/z/a.html
Files in zip:/x/y/z/d
-----
hello world
mkobit
sumber
0

Tidak ada jawaban yang bekerja untuk saya walaupun sumber daya saya dimasukkan ke dalam folder sumber daya dan mengikuti jawaban di atas. Yang membuat tipuan adalah:

@Value("file:*/**/resources/**/schema/*.json")
private Resource[] resources;
kukis
sumber
-5

Berdasarkan informasi @rob di atas, saya membuat implementasi yang saya rilis ke domain publik:

private static List<String> getClasspathEntriesByPath(String path) throws IOException {
    InputStream is = Main.class.getClassLoader().getResourceAsStream(path);

    StringBuilder sb = new StringBuilder();
    while (is.available()>0) {
        byte[] buffer = new byte[1024];
        sb.append(new String(buffer, Charset.defaultCharset()));
    }

    return Arrays
            .asList(sb.toString().split("\n"))          // Convert StringBuilder to individual lines
            .stream()                                   // Stream the list
            .filter(line -> line.trim().length()>0)     // Filter out empty lines
            .collect(Collectors.toList());              // Collect remaining lines into a List again
}

Walaupun saya tidak akan berharap getResourcesAsStreamuntuk bekerja seperti itu pada direktori, itu benar-benar berfungsi dan bekerja dengan baik.

Deven Phillips
sumber