'URI is not hierarchical' when loading a file from a .jar - java

I'm working on a Java based OpenGL project, which needs to load textures at runtime.
It's a Maven managed project and when packaged all files are placed inside one .jar.
I have the following piece of code which tries to load a texture:
URL url = Utils.class.getResource("/car.jpg");
TextureIO.newTexture(new File(url.toURI()), false);
That code works fine if I run the project from within Eclipse, but when packaged and executed from command line I get the following error (which is referring to the second line above):
java.lang.IllegalArgumentException: URI is not hierarchical
How can that line be re-written so the URI is correct, assuming I need to use getResourceAsStream(), but I need to pass it's value into a new File()?

You are trying to access it as a File, but when you package it in a jar there is no car.jpg file anymore, it is zipped inside the jar file.
So, you can't crate a File object pointing to it, the URL in this case is not a normal file URL but a "special" file-inside-the-jar URL.
Java is able to decode these URLs, or to access the resource directly and give you an InputStream.
The newTexture method also accepts either directly the URL or an InputStream.
To use the URL :
TextureIO.newTexture(url, false,"jpg");
To use the input stream :
InputStream instr = null;
try {
instr = Utils.class.getResourceAsStream("/car.jpg");
TextureIO.newTexture(instr, false, "jpg");
} finally {
instr.close();
}

Related

Java class.getRessource().getPath() adds a weird '/' at the begining of the URL

I want to load a font in a SWT. My ttf file is in the resources/fonts directory of my Maven project. I try to load it like this:
URL fontURL = MyClass.class.getResource("/fonts/myfont.ttf");
boolean fontLoaded = display.loadFont(fontURL.getPath());
But the resulting boolean is always false. I tried to prompt the result of fontURL.getPath(), and it is something like /C:/Users/myuser/Documents/.... If I copy this result in a String, remove the first / and try to call display.loadFont() with it, it works.
Another weird thing is that this is not the only resource I load this way. For example, this is how I load the icon of the window:
URL iconURL = MyClass.class.getResource("/images/myicon.png");
Image icon = new Image(display, iconURL.getPath());
shell.setImage(icon);
And it works fine. The only file posing problem is the font file. Does anybody know why ?
The reason for / at the beginning is that getPath of the URL class returns the URL path defined by RFC 2396 (see javadocs).
As for why it's working for the Image constructor and not for loadFont() method, the answer can be found in the implementation.
The constructor uses FileInputStream which internally normalizes the path, whereas loadFont() has a native implementation for loading which does not support such path.
Since in both cases a file path is expected, what you want to do is normalize the path yourself using either File constructor or Paths.get(url.toURI()).toString() method.

Java: Getting resource path of the main app instead of jar's

A lot has been discussed already here about getting a resource.
If there is already a solution - please point me to it because I couldn't find.
I have a program which uses several jars.
To one of the jars I added a properties file under main/resources folder.
I've added the following method to the jar project in order to to read it:
public void loadAppPropertiesFile() {
try {
Properties prop = new Properties();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
String resourcePath = this.getClass().getClassLoader().getResource("").getPath();
InputStream stream = loader.getResourceAsStream(resourcePath + "\\entities.properties");
prop.load(stream);
String default_ssl = prop.getProperty("default_ssl");
}catch (Exception e){
}
}
The problem (?) is that resourcePath gives me a path to the target\test-clasess but under the calling application directory although the loading code exists in the jar!
This the jar content:
The jar is added to the main project by maven dependency.
How can I overcome this state and read the jar resource file?
Thanks!
I would suggest using the classloader used to load the class, not the context classloader.
Then, you have two options to get at a resource at the root of the jar file:
Use Class.getResourceAsStream, passing in an absolute path (leading /)
Use ClassLoader.getResourceAsStream, passing in a relative path (just "entities.properties")
So either of:
InputStream stream = getClass().getResourceAsStream("/entities.properties");
InputStream stream = getClass().getClassLoader().getResourceAsStream("entities.properties");
Personally I'd use the first option as it's briefer and just as clear.
Can you try this:
InputStream stream = getClass().getClassLoader().getResourceAsStream("entities.properties")

Deploying images used in Java Application with the built jar file

After using images for example on a Button, when I build the application creating the .jar file and execute only the file, the images are not there but would only show if I copy the images folder in the same directory as the jar file. Why is that and how can I resolve this if possible?
I am currently using the following code to set the icon/image:
JButton btn = new JButton("Text", "img/icon.png");
The fact that you can use the images when they are stored outside the jar, suggests that you are doing something of the kind:
File image = new File("directory/image.jpg");
InputStream is = new FileInputStream(image);
This reads a file from a directory on the file system, not from the classpath. Now, if you have packaged your image in a "directory" inside your Jar, you must load the image from the classpath.
InputStream is = getClass().getResourceAsStream("/directory/image.jpg");
(note the slash in the path)
Or
InputStream is = getClass().getClassLoader().getResourceAsStream("directory/image.jpg");
(note the absence of the slash in the path)
Your example, as it is now, should not compile. (The second argument of your JButton construtor is an Icon, not a String, java 8). So when you were getting the image from the file system, you were probably doing something else.
With your example, you need to read an image from the inputstream and convert it to an Icon:
try (InputStream is = getClass.getClassLoader().getResourcesAsStream("directory/image.jpg")) {
BufferedImage image = ImageIO.read(is);
return new JButton("Text", new ImageIcon(image));
} catch (IOException exc) {
throw new RuntimeException(exc);
}
That should use the image that is located in "directory" inside your jar. Of course, you need to include the image within your jar, or you will get a NullPointerException on the inputstream is.
I think you need to understand the
ClassLoader:
A typical strategy is to transform the name into a file name and then
read a "class file" of that name from a file system.
So with this you will be able to lead Resources of your project with getResource
public URL getResource(String name)
Finds the resource with the given name. A resource is some data
(images, audio, text, etc) that can be accessed by class code in a way
that is independent of the location of the code.

Executing jar issue - uncaught error fetching image

I've made an audio player and the jar was made with netbeans. To load the images I've used:
ClassLoader cl = this.getClass().getClassLoader();
URL playerIconURL = cl.getResource("tp/audioplayer/Images/icon.png");
if (playerIconURL != null){
ImageIcon playerIcon = new ImageIcon(playerIconURL);
frame.setIconImage(playerIcon.getImage());
}
else{
System.err.println("cannot load player icon");
}
I mention that the folder Images is in the src/tp/audioplayer.
When I'm running the application inside netbeans everything is allright, but when I execute the jar in command prompt,the application starts but it's blank and it blocks and I get:
Can you tell me what I've done wrong or what is the problem? Thanks in advance!
If tp is in your classpath you will have to load it with cl.getResource("/tp/audioplayer/Images/icon.png") if tp is NOT a source folder (but still added to the buildpath.
If you add tp as a sourcefolder then
cl.getResource("/audioplayer/Images/icon.png")
Note that jars are casesensitive, make sure you the case-sensitive file-path.
Try any of these:
// using getResourceAsStream
InputStream is = this.getClass().getResourceAsStream( "picture.gif" );
// or
InputStream is = MyClass.class.getResourceAsStream( "stuff.ser" );
// or
InputStream is = MyApp.class.getClassLoader().getResourceAsStream( "InWords.properties" );
The resource in the jar file must be qualified with same package name as the class you call getResourceAsStream from. Alternatively, you can use an absolute name beginning with a / where dots get mapped to /s. If you don’t have a lead /, you have a relative name, and the name of the package will be prepended. If you use a /, you must include the name of the package yourself, or whatever name the resource is filed under in the jar.
For example you could specify /com/mindprod/mypackage/mystuff.ser or /com.mindprod.mypackage.mystuff.ser or simply mystuff.ser. Don’t use Windows style filenames with . These are not filenames, but Java resources that live along with the class files either in jars or sometimes freestanding on disk, or on the server.
In theory, getResourceAsStream will look in the local classpath, in the jar and in the directory where the class file was loaded from.

JarInputStream: getNextJarEntry always returns null

I have an I18n helper class that can find out the available Locales by looking at the name of the files inside the application's Jar.
private static void addLocalesFromJar(List<Locale> locales) throws IOException {
ProtectionDomain domain = I18n.class.getProtectionDomain();
CodeSource src = domain.getCodeSource();
URL url = src.getLocation();
JarInputStream jar = new JarInputStream(url.openStream());
while (true) {
JarEntry entry = jar.getNextJarEntry();
if (entry == null) {
break;
}
String name = entry.getName();
// ...
}
}
Currently, this isn't working - jar.getNextJarEntry() seems to always return null. I have no idea why that's happening, all I know is that url is set to rsrc:./. I have never seen that protocol, and couldn't find anything about it.
Curiously, this works:
class Main {
public static void main(String[] args) {
URL url = Main.class.getProtectionDomain().getCodeSource().getLocation();
JarInputStream jar = new JarInputStream(url.openStream());
while (true) {
JarEntry entry = jar.getNextJarEntry();
if (entry == null) {
break;
}
System.out.println(entry.getName());
}
}
}
In this version, even though there is practically no difference between them, the url is correctly set to the path of the Jar file.
Why doesn't the first version work, and what is breaking it?
UPDATE:
The working example really only works if I don't use Eclipse to export it. It worked just fine in NetBeans, but in the Eclipse version the URL got set to rsrc:./ too.
Since I exported it with Package required libraries into generated JAR library handling, Eclipse put its jarinjarloader in my Jar so I can have all dependencies inside it. It works fine with the other settings, but is there any way to make this work independently of them?
Another question
At the moment, that class is part of my application, but I plan to put it in a separate library. In that case, how can I make sure it will work with separate Jars?
The problem is the jarinjarloader ClassLoader that is being used by Eclipse. Apparently it is using its own custom rsrc: URL scheme to point to jar files stored inside the main jar file. This scheme is not understood by your URL stream handler factory, so the openStream() method returns null which causes the problem that you're seeing.
This answers the second part of your question about separate jars - not only will this work, it's the only way that it will work. You need to change your main application to use separate jars instead of bundling them all up inside the main jar. If you're building a web application, copy them into the WEB-INF/lib directory and you're fine. If you're building a desktop application, add a relative path reference in the META-INF/MANIFEST.MF to the other jars, and they will automatically be included as part of the classpath when you run the main jar.
The code may or may not result into the jar file where I18n resides. Also getProtectionDomain can be null. It depends how the classloader is implemented.
ProtectionDomain domain = I18n.class.getProtectionDomain();
CodeSource src = domain.getCodeSource();
URL url = src.getLocation();
about the rsrc:./ protocol, the classloader is free to use whatever URL they please (or name it for that matter)
try this out, you might get lucky :)
URL url = getClass().getResource(getClass().getSimpleName()+".class");
java.net.JarURLConnection conn = (java.net.JarURLConnection) url.openConnection();
Enumeration<JarEntry> e = conn.getJarFile().entries();
...
and good luck!
Eclipse's jarinjarloader loads everything using the system classloader and it never knows what jar file it was loaded from. That's why you can't get the jar URL for a rsrc: url.
I suggest storing the list of locales in a file in each application jar, e.g. META-INF/locales. Then you can use ClassLoader.getResources("META-INF/locales") to get the list of all the files with that name in the classpath and combine them to obtain the full list of locales.
I use System.getProperty("java.class.path") for getting the location of the jar. I do not know if that makes a difference. I have not explored the ProtectDomain path so I cannot help you there, sorry. As for multiple jars, just iterate through those jar file also.

Categories

Resources