1

I am working on a multi module Java project, it consists of multiple projects in Eclipse that depend on each other, now to add GUI Theme support I have created a Java project that does not contain any code, just the icons and pictures needed for the GUI, I made it a Java project so Maven will build it into a .jar. Now there's a main Application that loads the multiple projects into a main GUI, which gets its icons from resources within the actual modules at the moment. All I want to do is load all these resources from the external dummy .jar that is included in the classpath of the main application's .jar. Every method I have found so far does not seem to work. The .jar contains no actual java .classes, so there is no ClassLoader to reference. Are there any other methods to load the pictures without extracting the .jar?

user1307791
  • 11
  • 1
  • 2
  • This question looks like a duplicate of http://stackoverflow.com/questions/403256/how-do-i-read-a-resource-file-from-a-java-jar-file – Kappei Apr 16 '12 at 14:58
  • 1
    *"he .jar contains no actual java .classes, so there is no ClassLoader to reference."* If the Jar is on the run-time class-path of the app., the resources should be found using getResource() (in the context class loader) with an 'path from root' such as `/path/to/the.png` – Andrew Thompson Apr 16 '12 at 14:59
  • It was actually a simple error on my part (I used a non existent path). Apparently Java has no problem with grabbing resources from the classpath no matter where getResource() is called from. Someone explained to me that Java basically builds a filesystem out of all the used jars when it's run, so the access is quite simple and globally available. Thanks for the suggestions everyone! – user1307791 Apr 20 '12 at 18:06

3 Answers3

0

Three things you might try:

  1. Create a dummy class in the resource jar, which you could use to get the ClassLoader reference.
  2. Use URLClassLoader, if you know, where your jar-File resides.
  3. You always have the possibility to create your own ClassLoader by extending java.lang.ClassLoader.
henrik
  • 708
  • 7
  • 14
0

Treat the external jar file as a zip-archive and read the images/resource something like this:

public ZipStuff(String filename) throws IOException
{
    ZipFile zf = new ZipFile(filename);
    Enumeration<? extends ZipEntry> entries = zf.entries();

    while (entries.hasMoreElements()) {
      ZipEntry ze = entries.nextElement();
      ImageIcon ii = new ImageIcon(ImageIO.read(zf.getInputStream(ze)));
      JLabel l = new JLabel(ii);
      getContentPane().add(l);
    }
    setVisible(true);
    pack();
}

In this case I have a jar file with one single image which I load into a JLabel. The ZipStuff-class is a JFrame.

Kennet
  • 5,736
  • 2
  • 25
  • 24
0

You could get all of the resources (all jar files on classpath should work even one without classes):

    Enumeration<URL> resources = null;
    try {
        resources = Thread.currentThread().getContextClassLoader().getResources(someResource);
    } catch (Exception ex) {
         //no op      
    }
    if (resources == null || !resources.hasMoreElements()) {
        resources = ClasspathReader.class.getClassLoader().getResources(someResource);
    }

Then check to see if the current resource is a file. Files you can deal with direct as files. But your question was about jar files so I wont go there.

    while (resources.hasMoreElements()) {
        URL resource = resources.nextElement();


        if (resource.getProtocol().equals("file")) {
            //if it is a file then we can handle it the normal way.
            handleFile(resource, namespace);
            continue;
        }

At this point you should have only jar:file resources so... Split up the string that looks like this:

jar:file:/Users/rick/.m2/repository/invoke/invoke/1.0-SNAPSHOT/invoke-1.0-SNAPSHOT.jar!/org/node/

into this

/Users/rick/.m2/repository/invoke/invoke/1.0-SNAPSHOT/invoke-1.0-SNAPSHOT.jar

and this

/org/node/

Here is the code to do the above devoid of pesky error checking. :)

        String[] split = resource.toString().split(":");
        String[] split2 = split[2].split("!");
        String zipFileName = split2[0];
        String sresource = split2[1];

        System.out.printf("After split zip file name = %s," +
                " \nresource in zip %s \n", zipFileName, sresource);

Now we have the zip file name so we can read it:

        ZipFile zipFile = new ZipFile(zipFileName);

Now we can iterate through its entries:

        Enumeration<? extends ZipEntry> entries = zipFile.entries();

        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();

            /* If it is a directory, then skip it. */
            if (entry.isDirectory()) {
                continue;
            }

            String entryName = entry.getName();
            System.out.printf("zip entry name %s \n", entryName);

See if it starts with the resource we are looking for.

            if (!entryName.startsWith(someResource)) {
                continue;
            }

There were two tricks I did earlier to see if it was a directory

    boolean isDir = !someResource.endsWith(".txt");

This only works because I am looking for resources that ends with .txt and I assume if it does not end with .txt that it is an dir /foo/dir and /foo/dir/ both work.

The other trick was this:

    if (someResource.startsWith("/")) {
        someResource = someResource.substring(1);
    }

Classpath resource can never really start with a starting slash. Logically they do, but in reality you have to strip it. This is a known behavior of classpath resources. It works with a slash unless the resource is in a jar file. Bottom line, by stripping it, it always works.

We need to get the actual fileName of the darn thing. The fileName part from the entry name. where /foo/bar/foo/bee/bar.txt, and we want 'bar.txt' which is the fileName. Back inside of our while loop.

        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            ...
            String entryName = entry.getName(); //entry is zipEntry
            String fileName = entryName.substring(entryName.lastIndexOf("/") + 1);


            /** See if the file starts with our namespace and ends with our extension. */
            if (fileName.startsWith(namespace) && fileName.endsWith(".txt")) {

Next we see if these entry matches our criteria and if so read contents of the file to System.out.

          try (Reader reader = new InputStreamReader(zipFile.getInputStream(entry))) {
                    StringBuilder builder = new StringBuilder();
                    int ch = 0;
                    while ((ch = reader.read()) != -1) {
                        builder.append((char) ch);

                    }
                    System.out.printf("zip fileName = %s\n\n####\n contents of file %s\n###\n", 
                    entryName, builder);
                } catch (Exception ex) {
                    ex.printStackTrace();//it is an example/proto :)
                }
            }

You can see the full example here: Sleepless in Pleasanton.

RickHigh
  • 1,808
  • 20
  • 16