Home > File handling > Listing a directory on the class path when it’s in a Jar archive

Listing a directory on the class path when it’s in a Jar archive

Today I was revisiting an old memory game that I wrote almost seven years ago and I must admit that I was partly horrified by the code I have written back in the days. So I started to change small parts of it. For example I was loading a set of images from a directory on the class path, by generating the name of the image, like “card1.jpg”, “card2.jpg” and getting these via getClass().getResource(name). Now, as I was changing some images, and files happened to be “.jpg” and others “.png”, my old name-generator code failed. Not a good solution, I thought, and planned to load all files from a directory that would just contain card images – didn’t work as expected though, but I found a solution on the net, which I had to change a bit to make it work for me. Here’s the result that I’d like to share.

/**
* List directory contents for a resource folder into a target collection. Not recursive. 
* This is basically a brute-force implementation. Works for regular files and also JARs.
* Based on an approach by Greg Briggs
* 
* @param clazz any java class that lives in the same place as the resources you want.
* @param path should end with "/", but not start with one.
* @param result the collection to store the result.
* @return the target collection, 
*         where just the name of each member item, not the full path, is added.
* @throws URISyntaxException
* @throws IOException
*/
static <T extends Collection<? super String>> T getFileListing(
    final Class<?> clazz, final String path, final T result) throws URISyntaxException, 
                                                                   IOException {
 URL dirURL = clazz.getResource(path);
 if (dirURL != null && dirURL.getProtocol().equals("file")) {
  result.addAll(Arrays.asList(new File(dirURL.toURI()).list()));
  return result;
 }
 if (dirURL == null) {
  // In case of a jar file, we can't actually find a directory. 
  // Have to assume the same jar as clazz.
  final String me = clazz.getName().replace(".", "/") + ".class";
  dirURL = clazz.getResource(me);
 }
 if (dirURL.getProtocol().equals("jar")) { /* A JAR path */
  // strip out only the JAR file
  final String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!"));
  final JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
  final Enumeration<JarEntry> entries = jar.entries(); // gives ALL entries in jar
  while (entries.hasMoreElements()) {
   final String name = entries.nextElement().getName();
   final int pathIndex = name.lastIndexOf(path);
   if (pathIndex > 0) {
    final String nameWithPath = name.substring(name.lastIndexOf(path));
    result.add(nameWithPath.substring(path.length()));
   }
  }
  jar.close();
  return result;
 }
 throw new UnsupportedOperationException("Cannot list files for URL " + dirURL);
}

Here is the original code my Greg Briggs (you saved my day, thanks!):
http://www.uofr.net/~greg/java/get-resource-listing.html.
To make it work for me, I just had to remove some “getClassLoader()” calls, change the path detection code a bit and close the jar file. The other change I did was to inject the target collection to the method, so the user can chose which collection type is appropriate, of if he wants to just add to an existing collection.

Usage example:

Let’s just demonstrate how I’m actually using the code above.
My file structure looks like this:

de/brix/plush/memory/images/
de/brix/plush/memory/images/card1.jpg
de/brix/plush/memory/images/card2.jpg
de/brix/plush/memory/images/card3.jpg
de/brix/plush/memory/Card.class
de/brix/plush/memory/Main.class
de/brix/plush/memory/Playfield.class
//…

Now here is a snippet of code that uses the “getFileListing” method:

class Playfield{
 private static final String IMAGE_DIR =”images/”;
//…
 private void loadImages(final Collection<Image> images) 
              throws URISyntaxException,  IOException, InterruptedException {
  int trackerID = 0;
  for (final String fileName : getFileListing(getClass(),IMAGE_DIR, new HashSet<String>())) {
   final URL url = getClass().getResource(IMAGE_DIR + fileName);
   final Image image = Toolkit.getDefaultToolkit().getImage(url);
   tracker.addImage(image, ++trackerID);
   images.add(image);
  }
  tracker.waitForAll();
 }
}

The great thing about the code is, that it works whether I’m loading directly from the file system in my IDE, or whether I the images are packaged in a Jar archive on the class path, as part of a Webstart applet.

Categories: File handling Tags: , , ,
  1. No comments yet.
  1. No trackbacks yet.

Leave a comment