9

my problem is that I create a folder(name is IconResources) under src , in IconResources there are many pictures. Directory is like this:

  • ProjectName
    • src
      • package 1
      • package 2
      • IconResources(this is the target folder)

I want to list all picture files name and do something with those pictures. And I found that File.list() only works in IDE, if I export project to a jar to make a standalone, it cannot work. So I searched and found that I should use inputstream and outputstream. Now I find i/o stream can works well for single file. But I want to use inputstream and outputstream to read folder(IconResource), then list the files in that folder.

So my question is how to use i/o put stream to load a folder and iterate the folder to list file names in that folder. These things should work under not only IDE but also exported jar. My code are follows:

private void initalizeIconFiles(File projectDirectory){

    URL a = editor.getClass().getResource("/IconResources/");// Get Folder URL
    List<String> iconFileNames=new ArrayList<String>();//Create a list to store file names from IconResource Folder
    File iconFolder = new File(a.getFile());
    Path path=Paths.get(iconFolder.getPath());
    DirectoryStream<Path> stream = Files.newDirectoryStream(path) ;
    for (Path entry : stream) {
        if (!Files.isDirectory(entry)) {
            System.out.println(entry.getFileName().toString());
            iconFileNames.add(entry.getFileName().toString());// add file name in to name list
        }
    }
}

above code only can run under IDE but break down in exported jar.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Li Yuan
  • 123
  • 1
  • 1
  • 7
  • try with `getClassLoader().getResourceAsStream()` instead – Jean-François Savard Mar 11 '15 at 11:28
  • From the [documentation for `Files.newDirectoryStream`](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#newDirectoryStream-java.nio.file.Path-) _When not using the try-with-resources construct, then **directory stream's close method should be invoked** after iteration is completed so as to free any resources held for the open directory._ (emphasis added) Make sure you read the documentation and do as it says, the current code is a memory leak. – Boris the Spider Mar 11 '15 at 12:05

4 Answers4

13

This is actually a very difficult question, as the ClassLoader has to take account of the fact that you might have another folder called IconResources on the classpath at runtime.

As such, there is no "official" way to list an entire folder from the classpath. The solution I give below is a hack and currently works with the implementation of URLClassLoader. This is subject to change.

If you want to robust approach, you will need to use a classpath scanner. There are many of these in library form, my personal favourite is Reflections, but Spring has one built in and there are many other options.

Onto the hack.

Generally, if you getResourceAsStream on a URLClassLoader then it will return a stream of the resources in that folder, one on each line. The URLClassLoader is the default used by the JRE so this approach should work out of the box, if you're not changing that behaviour.

Assume I have a project with the following classpath structure:

/
    text/
        one.txt
        two.txt

Then running the following code:

final ClassLoader loader = Thread.currentThread().getContextClassLoader();
try(
        final InputStream is = loader.getResourceAsStream("text");
        final InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
        final BufferedReader br = new BufferedReader(isr)) {
    br.lines().forEach(System.out::println);
}

Will print:

one.txt
two.txt

So, in order to get a List<URL> of the resources in that location you could use a method such as:

public static List<URL> getResources(final String path) throws IOException {
    final ClassLoader loader = Thread.currentThread().getContextClassLoader();
    try (
            final InputStream is = loader.getResourceAsStream(path);
            final InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
            final BufferedReader br = new BufferedReader(isr)) {
        return br.lines()
                .map(l -> path + "/" + l)
                .map(r -> loader.getResource(r))
                .collect(toList());
    }
}

Now remember, these URL locations are opaque. They cannot be treated as files, as they might be inside a .jar or they might even be at an internet location. So in order to read the contents of the resource, use the appropriate methods on URL:

final URL resource = ...
try(final InputStream is = resource.openStream()) {
    //do stuff
}
Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
  • Cool hack, wasn't aware `getResourceAsStream()` worked that way, by listing folder contents. Is this documented anywhere? – Harald K Mar 11 '15 at 12:08
  • 1
    @haraldK this is implementation specific behaviour and is not documented in the official documentation; hence it's a hack. It's unlikely to change however, as a large amount of existing code relies on this behaviour - Oracle don't like breaking changes. – Boris the Spider Mar 11 '15 at 12:13
  • 1
    @BoristheSpider I try your advice and it works pretty well in eclipse IDE. However, when I export project to a jar and run in standalone, it break down. Anyway, thank you for your suggestions. Do you have any idea about how to run correctly in jar? – Li Yuan Mar 12 '15 at 03:48
1

You can read contents of a JAR file with JARInputStream

  • This is not very helpful, as the OP wants the files inside the classpath. In order to find the correct jar you would need to somehow work out where it was from inside the application. – Boris the Spider Mar 11 '15 at 11:46
  • Also, a [ZIP file system](http://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html) would be a much better approach if going down this route. – Boris the Spider Mar 11 '15 at 12:01
1

Finally I work it out. I adopt Boris the Spider's answer, and it works well in IDE but break down in exported Jar. Then I change

final InputStream is = loader.getResourceAsStream("text");

to

final InputStream is = loader.getResourceAsStream("./text");

Then it works both in IDE and JAR.

Li Yuan
  • 123
  • 1
  • 1
  • 7
0

I tested that final InputStream is = loader.getResourceAsStream("text"); is working.

Yunnosch
  • 26,130
  • 9
  • 42
  • 54