4

I have a number of resource files I want to copy to another location as a method call. There are various how-tos and guides, and questions here on SO about how to read/copy files from a JAR. However if I am not completely off, the standard ways of getting a resource will be looking at different places when the code is run from a deployed JAR, compared to when the code is run from Eclipse while developing/debugging. So the solution that works for one case doesn't for the other..

My current and extremely verbose solution (see below) works, but it's ugly, long, and annoying to maintain (to add or remove an additional file). Is there way I can optimise this method, (possibly with new voodoo from NIO package) for instance by just loop over the files in the /res folder and just copy them all to the new location. I'm not sure how to avoid specifying the filenames and paths for getResource() would work in that scenario.

private void loadRes() throws IOException{
    // get styling resources from the jar file 
    File htmlRes = new File(topfolder, "res");
    if(!htmlRes.exists() || !htmlRes.isDirectory())
            htmlRes.mkdir();

    File cssfile = new File(htmlRes, "style.css"),
         bullet_file = new File(htmlRes,"i.gif"), 
         banner_file = new File(htmlRes, "banner.png"),
         bg_file = new File(htmlRes,"body_bg.png"),
         img_bg = new File(htmlRes,"button-bg.png"),
         hov_bg = new File(htmlRes,"button-hover-bg.png"),
         visu_img = new File(htmlRes,"visu.png"),
         pc_img = new File(htmlRes,"pc.png"),
         asc_gif = new File(htmlRes,"asc.gif"),
         desc_gif = new File(htmlRes,"desc.gif"),
         bg_gif = new File(htmlRes,"bg.gif");

    URL css_url = ExportUtils.class.getResource("/res/style.css");
    URL bullet_url = ExportUtils.class.getResource("/res/i.gif");
    URL banner_url = ExportUtils.class.getResource("/res/banner.png");
    URL bg_url = ExportUtils.class.getResource("/res/body_bg.png");
    URL image1_url = ExportUtils.class.getResource("/res/button-bg.png");
    URL image2_url = ExportUtils.class.getResource("/res/button-hover-bg.png");
    URL visu_url = ExportUtils.class.getResource("/res/visu.png");
    URL pc_url = ExportUtils.class.getResource("/res/pc.png");
    URL asc_url = ExportUtils.class.getResource("/res/asc.gif");
    URL desc_url = ExportUtils.class.getResource("/res/desc.gif");
    URL bggif_url = ExportUtils.class.getResource("/res/bg.gif");

    FileOutputStream fos = new FileOutputStream(cssfile);
    Resources.copy(css_url, fos);
    fos = new FileOutputStream(bullet_file);
    Resources.copy(bullet_url, fos);
    fos = new FileOutputStream(banner_file);
    Resources.copy(banner_url, fos);
    fos = new FileOutputStream(bg_file);
    Resources.copy(bg_url, fos);
    fos = new FileOutputStream(img_bg);
    Resources.copy(image1_url, fos);
    fos = new FileOutputStream(hov_bg);
    Resources.copy(image2_url, fos);
    fos = new FileOutputStream(visu_img);
    Resources.copy(visu_url, fos);
    fos = new FileOutputStream(pc_img);
    Resources.copy(pc_url, fos);
    fos = new FileOutputStream(asc_gif);
    Resources.copy(asc_url, fos);
    fos = new FileOutputStream(desc_gif);
    Resources.copy(desc_url, fos);
    fos = new FileOutputStream(bg_gif);
    Resources.copy(bggif_url, fos);

    // scripts and finish head
    URL sort_script = ExportUtils.class.getResource("/res/scripts.js");
    URL func_script = ExportUtils.class.getResource("/res/jquery.tablesorter.min.js");
    URL scinot_parser = ExportUtils.class.getResource("/res/jquery.tablesorter.scinot.js");

    File div_js = new File(htmlRes,"scripts.js");
    File jquery_sorttable = new File(htmlRes,"jquery.tablesorter.min.js");
    File jquery_st_scinot = new File(htmlRes, "jquery.tablesorter.scinot.js");

    fos = new FileOutputStream(div_js);
    Resources.copy(sort_script, fos);

    fos = new FileOutputStream(jquery_sorttable);
    Resources.copy(func_script, fos);

    fos = new FileOutputStream(jquery_st_scinot);
    Resources.copy(scinot_parser, fos);
 }
Community
  • 1
  • 1
posdef
  • 6,498
  • 11
  • 46
  • 94

7 Answers7

3

I suggest you to try Google Reflections . It's a library aimed to ease the use of Java reflections, but it turns out it's equally good to find resources in the classpath. To solve your specific problem, you could first get all the resources in /res usgin the following code:

Reflections reflections = new Reflections("res");
Set<String> resources = reflections.getResources(Pattern.compile(".*\\.*"));

Afterwards, you would need to get the contents of each resource in resources as an InputStream:

InputStream resourceInputSteam =  ClassLoader.class.getResourceAsStream(aResource);

And finally place the resources into a folder which you can easily do using Apache commons-io's IOUtils#copy:

IOUtils.copy(resourceInputSteam, destinationFileOutputStream);
Pau Carre
  • 471
  • 2
  • 5
1

You can get the location of your resources with

ExportUtils.class.getProtectionDomain().getCodeSource().getLocation()

If you are running in Eclipse, this should give you a file: URL pointing to the project output folder. If run from a Jar it should point to the Jar. You can then iterate over all files in the resource directory from the file folder or from a JarInputStream.

However, since this approach heavily depends on the execution environment (e. g. class loaders and security managers) this might or might not work. I'd therefore not recommend to use this in production. Instead I'd use your approach, possibly tidying it up a bit by defining a list of resources at the top and then processing them in a loop, reducing code duplication.

Michael Koch
  • 1,152
  • 11
  • 17
0

I usually use the directory of the class files and scan them.

File directory = MyClass.class.getResource("/."); // or "." dont remember it

Now you can use

for(File file : directory.listFiles()) { if(file.isDirectory()) scanDirectory(file); else { //check for resources and copy them } }

So you can finally walk the directory tree starting with the classpath entry. But check for packages like jars and so on. Being inside a jar might become more tricky.

PS: scanDirectory is the method that the for loop is in.

Martin Kersten
  • 5,127
  • 8
  • 46
  • 77
  • This looks more like a web deployment and tomcat and cohorts usually decompress war files (which can be switched off). Thats why I wrote "Being inside a jar might become more tricky." – Martin Kersten Mar 03 '15 at 09:22
0
private void loadRes() throws IOException{
    DataInputStream dis  = null;
    FileOutputStream fos  = null;
    try{
        File htmlRes = new File(".", "res");
    if(!htmlRes.exists() || !htmlRes.isDirectory())
        htmlRes.mkdir();

        File outfile = new File(htmlRes, "/res/style.css");
        fos = new FileOutputStream(outfile);
        InputStream is =  Test.class.getResourceAsStream("/res/style.css");
        dis = new DataInputStream(is);
        while (true){
            fos.write(dis.readByte());
        }
//repeat for rest of the files
// or set up an array of file names and iterate this in a loop 

    }catch(NullPointerException npe){
        System.err.println("NullPointerException : " + npe.getMessage();
}catch(EOFException eofe){
        try{
                //This ensures the streams are closed properly
                dis.close();
                fos.close();
        }catch(IOException ioe){
                System.err.println("IOException: " + ioe.getMessage());
        }
    }catch(MalformedURLException murle){
            System.err.println("MalformedURLException: " + murle.getMessage());
    }catch(IOException ioe){
            System.err.println("IOException: " + ioe.getMessage());
    }

}
0

RE-EDIT

You can browse a jar file by creating a Jar filesystem and finding its root directory. Then the FileVistor pattern can be used to pick up every file within the jar file. For the other case, the root directory is simply the directory where your files are.

Use getResource() to tell you if the class is loaded from a jar file or a directory. The output has to be manipulated into a form that later methods can use.

public class JarFs extends SimpleFileVisitor<Path> {
    public static void main(String[] args) throws IOException {

        URL resource = JarFs.class.getResource("JarFs.class");
        System.out.println(resource);
        boolean isJar = false;
        String jarFile;
        if( resource.toString().startsWith("jar")) {
            isJar = true;
            jarFile = resource.toString().replaceAll("!.*", "").replace("file:", "file://");
        } else {
            isJar = false;
            jarFile = resource.toString().replace("file:", "").replace("JarFs.class", "");
        }
        JarFs j = new JarFs(jarFile, isJar);
        j.browse();
    }
    Path root;
    JarFs(String jar, boolean isJar)  throws IOException {
        if(isJar) {
            //If the class is loaded from a jar file, create a Jar filesystem.
            String path = jar;
            System.err.println("Create " + path);
            URI uri = URI.create(path);
            Map<String, ?> env = Collections.emptyMap();
            FileSystem fs = FileSystems.newFileSystem(uri, env);
            Iterable<Path> roots = fs.getRootDirectories();
            for (Path p : roots) {
                root = p;
                break;
            }
        } else {
            //while if the class is loaded from a file, use the directory it is loaded from 

            root = FileSystems.getDefault().getPath(jar);
        }
    }

Once this is set up you can walk your filesystem or directory like this

    void browse() throws IOException {
        Files.walkFileTree(root, this);
    }

The payoff is in the visitFile() method

    @Override
    public FileVisitResult visitFile(Path file,
                               BasicFileAttributes attr) {
        if (attr.isRegularFile()) {
            // Do the business here
        }
        return FileVisitResult.CONTINUE;
    }
}
Neil Masson
  • 2,609
  • 1
  • 15
  • 23
  • I might have misunderstood something here, but it looks like your `JarFS` is meant to read an arbitrary jar file. I was thinking about the jar that the code itself is deployed from. Also do you have any suggestions regarding how to automatically switch from regular File traversal and the proposed Jar-traversal at runtime, depending on how the project is launched? – posdef Mar 04 '15 at 10:32
  • You can use getResource() to find out where the class is loaded from, as in my edited answer. – Neil Masson Mar 04 '15 at 12:57
0

To get a list of all files I use the following implementation. This works during development in eclipse and from within the jar file as well.

(The code uses Lombok, so you have to replace the val's with the 'real' types and instead of @Cleanup close() the JarFile at the end)

public final class ResourceUtils {

  public static List<String> listFiles(Class<?> clazz, String path) throws IOException {
    val files = new ArrayList<String>();

    val url = clazz.getResource(path);
    if (url == null) throw new IllegalArgumentException("list files failed for path '" + path + "'.");

    if ("file".equals(url.getProtocol())) {
      try {
        val rootDir = new File(url.toURI());
        findFilesRecursive(files, rootDir, rootDir);
        return files;
      }
      catch (URISyntaxException e) {
        throw new AssertionError(e); // should never happen
      }
    }

    if ("jar".equals(url.getProtocol())) {
      if (path.startsWith("/")) path = path.substring(1);
      val jarFile = url.getPath().substring(5, url.getPath().indexOf("!"));
      @Cleanup val jar = new JarFile(jarFile);
      val entries = jar.entries();
      while (entries.hasMoreElements()) {
        val entry = entries.nextElement();
        if (!entry.isDirectory()) {
          val name = entry.getName();
          if (name.startsWith(path)) files.add(name.substring(path.length() + 1));
        }
      }

      return files;
    }

    throw new IllegalArgumentException("Unexpected protocol: " + url.getProtocol());
  }

  private static void findFilesRecursive(List<String> files, File rootDir, File currPath) {
    if (currPath.isDirectory()) {
      for (File path : currPath.listFiles()) {
        findFilesRecursive(files, rootDir, path);
      }
    }
    else {
      files.add(currPath.getAbsolutePath().substring(rootDir.getAbsolutePath().length() + 1));
    }
  }
}

Call it like this:

ResourceUtils.listFiles(MyClass.class, "/res");
tomse
  • 501
  • 2
  • 7
0

You could grep the class paths from the environment. This would allow you the unzip the jar files within the defined locations and filter through the zip file entries, say everything but .class.

Hannes
  • 2,018
  • 25
  • 32