Crazy jar resource access problem

J

JavaEnquirer

An application gets launched - application.jar. The application calls a
method on a second jar - utility.jar. This method's job is to load a
resource from application.jar. How do you achieve this? When
utility.jar attempts to access a jar resource, the usual method of
using a class loader doesn't work, as its class loader is for itself,
not application.jar!

How do you get around this? I've tried a few things e.g.

1. adding application.jar to utility.jar's class path ( horrid coupling
),

2. passing a reference to application.jar's class loader to utility.jar
to use to access application.jar's resources.

Neither works!! Any ideas? This is really frustrating and I can't
believe that no one else has this problem. Surely, when developing
desktop applications, it's common practice to farm out resource
management code to an external, reusable jar, and then expect it to
access resources in the main application jar. Aargh! Many thanks in
advance.
 
J

JavaEnquirer

Ah, just one other thing. I've posted this problem a couple of times
and a number of people have kindly replied. However, I haven't done a
good job of explaining the problem. I'm not simply trying to load an
image from within the same jar as the code interested in the image.
That's straight forward. Neither am I trying to get the main
application jar to load an image from a secondary jar referenced by the
main jar's classpath.

I'm trying to get the utility jar to access a resource in the main jar
- the reverse of what's normally going on! And, as its a utility jar,
to be reused, it shouldn't reference the main application jar on its
classpath.

I'd have thought this was a classic - resource loading code in a
utility jar able to load resources from any arbitrarily specified jar
at runtime.
 
C

Chris Uppal

JavaEnquirer said:
I'm trying to get the utility jar to access a resource in the main jar
- the reverse of what's normally going on! And, as its a utility jar,
to be reused, it shouldn't reference the main application jar on its
classpath.

I'd have thought this was a classic - resource loading code in a
utility jar able to load resources from any arbitrarily specified jar
at runtime.

I think you'll have to pass a "token" from the main application to the utility
library to say /where/ to look for the resource. The utility library should
not "know" about the main application's JAR (or anything else about it). The
obvious "tokens" to pass are either the classloader of the main application or
one or more of the java.lang.Class objects from the app.

You then have the choice of building some sort of registry in the utility
library which the application populates with tokens as part of its
initialisation (or later, I suppose), or of designing the API so that you
always pass a token as a parameter to any call to a resource-loading utility
method. (Or a hybrid of the two.)

Or maybe you've already tried that and it didn't work ? (I didn't follow your
earlier thread.)

-- chris
 
J

JavaEnquirer

I think you'll have to pass a "token" from the main application to the utility
library to say /where/ to look for the resource. The utility library should
not "know" about the main application's JAR (or anything else about it). The
obvious "tokens" to pass are either the classloader of the main application or
one or more of the java.lang.Class objects from the app.

You then have the choice of building some sort of registry in the utility
library which the application populates with tokens as part of its
initialisation (or later, I suppose), or of designing the API so that you
always pass a token as a parameter to any call to a resource-loading utility
method. (Or a hybrid of the two.)

Or maybe you've already tried that and it didn't work ? (I didn't follow your
earlier thread.)

Cheers. Yep I tried this but to no avail!
 
C

Chris Uppal

JavaEnquirer said:
Cheers. Yep I tried this but to no avail!

What's the problem ? It works for me (details follow below). What are you
doing, or needing to do, differently ?

-- chris

===== App.java =======
package application;

import utilities.Utils;

public class App
{
public static void
main(String[] args)
{
new App().doit();
}

private void
doit()
{
Class<?> c = this.getClass();

System.out.println("Res1: " + c.getResource("res1.txt"));
System.out.println("Res2: " + Utils.findResourceFor(c, "res2.txt"));
}
}
============

====== Utils.java ======
package utilities;

public class Utils
{
public static java.net.URL
findResourceFor(Class<?> c, String name)
{
return c.getResource(name);
}
}
============

Compile and build JARs, then:

$ jar -tf A.jar
META-INF/
META-INF/MANIFEST.MF
application/
application/App.class
application/res1.txt
application/res2.txt

$ jar -tf U.jar
META-INF/
META-INF/MANIFEST.MF
utilities/
utilities/Utils.class

$ java -cp "U.jar;A.jar" application.App
Res1: jar:file:/C:/Home/tmp/app-res/A.jar!/application/res1.txt
Res2: jar:file:/C:/Home/tmp/app-res/A.jar!/application/res2.txt
 
J

JavaEnquirer

Thank you very much for your time and effort, its much appreciated.
However, the code you've suggested still doesn't work with my
particular configuration and needs. The only differences between what
I've been doing and what you suggest is, that I don't store my
resources relative to the class, instead I store them in a jar top
level folder called images.
Therefore, c.getResource isn't appropriate ( as far as I'm aware ), but
c.getClassLoader().getResource() is. However, as soon as this code goes
inside a jar it fails to work. I'm at a loss. Once again thanks for
your help. Maybe I'll try and post a simplified version of my code
complete with jar details as you have done.

Chris said:
JavaEnquirer said:
Cheers. Yep I tried this but to no avail!

What's the problem ? It works for me (details follow below). What are you
doing, or needing to do, differently ?

-- chris

===== App.java =======
package application;

import utilities.Utils;

public class App
{
public static void
main(String[] args)
{
new App().doit();
}

private void
doit()
{
Class<?> c = this.getClass();

System.out.println("Res1: " + c.getResource("res1.txt"));
System.out.println("Res2: " + Utils.findResourceFor(c, "res2.txt"));
}
}
============

====== Utils.java ======
package utilities;

public class Utils
{
public static java.net.URL
findResourceFor(Class<?> c, String name)
{
return c.getResource(name);
}
}
============

Compile and build JARs, then:

$ jar -tf A.jar
META-INF/
META-INF/MANIFEST.MF
application/
application/App.class
application/res1.txt
application/res2.txt

$ jar -tf U.jar
META-INF/
META-INF/MANIFEST.MF
utilities/
utilities/Utils.class

$ java -cp "U.jar;A.jar" application.App
Res1: jar:file:/C:/Home/tmp/app-res/A.jar!/application/res1.txt
Res2: jar:file:/C:/Home/tmp/app-res/A.jar!/application/res2.txt
 
C

Chris Uppal

JavaEnquirer said:
Thank you very much for your time and effort, its much appreciated.

You're welcome, it's something I wanted to get straight in my own mind, and
once I'd done that then why not post the code too ?

However, the code you've suggested still doesn't work with my
particular configuration and needs. The only differences between what
I've been doing and what you suggest is, that I don't store my
resources relative to the class, instead I store them in a jar top
level folder called images.

That still works for me. Change my code to load:

System.out.println("Res1: " + c.getResource("/resources/res1.txt"));
System.out.println("Res2: " + Utils.findResourceFor(c,
"/resources/res2.txt"));

and change the A.jar so that:

$ jar tf A.jar
META-INF/
META-INF/MANIFEST.MF
application/
application/App.class
resources/
resources/res1.txt
resources/res2.txt

and then running it gives:

$ java -cp "U.jar;A.jar" application.App
Res1: jar:file:/C:/Home/tmp/app-res/A.jar!/resources/res1.txt
Res2: jar:file:/C:/Home/tmp/app-res/A.jar!/resources/res2.txt

Maybe the problem is to do with case-sensitivity, or you are using '\\'
separators instead of '/' somewhere (either in your code or in the JAR file
itself).

Another possibility is that you are getting confused by the (undoubtedly
confusing) difference between java.lang.Class.getResource() and
ClassLoader.getResource(). For a class object, the absolute (i.e. relative to
the archive's root rather than to the classfile itself) path must start with
'/' -- which makes sense. OTOH, ClassLoader.getResource() expects a name NOT
to start with '/' -- which also makes sense because a leading '/' would mean
the root of the filesystem for classloaders looking at directories rather than
JARfiles (and also because names in JAR/ZIP files must not have a leading '/').

-- chris
 
J

JavaEnquirer

Apologies everyone, I've been a bit on an idiot here. I was using a
File.separator buried deep in my code instead of a /, which is of
course the internal separator used by the zip/jar format. I was caught
out by unthinkingly and mistakenly going for what I thought was an file
system independent approach, when the problem was to do with an
individual file format. I spent my time worrying about class loaders
when the gotcha was a beginner's error.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top