Reputation: 17
I've mostly only created application for personal use and the rare occasions where I have distributed my code have been in the form of uploading my source code on GitHub. I'm currently finishing up a project and plan on using launch4j to package it up as an exe. However, my application has a handful of png files that I coded in with the unique filepath of my computer. Obviously if my code were to run on any other computer in the world, those files would not be found. I'm vaguely aware that java does not require the full filepath for a file (ie C:\Users...\file_name.ext) but I've never gotten a program to run correctly unless I write out the filepath like that, so that's been my default up until this point.
Upvotes: 0
Views: 481
Reputation: 103773
The resource system. Think about it: What's the difference between the many class files that comprise your application, and those png files, from an application distribution perspective?
The answer is, essentially, nothing. They are file-like concepts, they might prefer to be shipped in a packaged-up file (a jar file) instead of separately. They must be found at some point halfway through your app's existence (java does not pre-load all classes. It just loads your main class, and then loads whatever is needed the first time you mention any class).
You don't have to hardcode the absolute path to those class files in your app, so they clearly don't suffer from this 'coding filepaths' issue.
Thus, the answer is somewhat obvious: Simply stick those PNG files in the exact same place as your class files, and ask the VM to provide you with the data in them using whatever mechanism it is using itself, as it is doing that exact same job (find resource, obtain data in the resource) all the time, on your class files.
But, how?
You have 2 different methods, and these 2 methods take the same kind of argument, which comes in 2 forms: A grand total of 4 'modes' to choose from.
If the API you have that needs an image file so happens to have an overload that accepts a URL, this is very simple (ImageIcon
is one such resource, that's probably what you're passing these PNG files to, so that's great):
URL loadIcon = ContextClass.class.getResource("/icons/load.png");
new ImageIcon(loadIcon);
Quite simple. Sometimes you want to read it yourself directly, and a URL
object is rather unwieldy. Sometimes, you want to pass it to an API which does not have a URL
overload, but it does have an InputStream overload. Then, you can fetch an InputStream. Given that this is a resource, like all resources, you must safely close it, thus, let's use try-with:
byte[] pngData;
try (var in = ContextClass.class.getResourceAsStream("/icons/load.png")) {
pngData = in.readAllBytes();
}
ContextClass.class
is a somewhat exotic java syntax feature: It is an expression that resolves to the java.lang.Class
instance of the so-named class. For example, Class<?> c = String.class;
is legal java and gives you the class object that represents the class concept of all java.lang.String
objects. The class object itself has these getResource
methods. Thus, substitute some relevant class that you wrote as context here. Presumably, if you want to load an image in source file MyStatusWindow.java
, you'd just use that class: MyStatusWindow.class.getResource
.
These methods will look in the same location that the class itself was loaded from. If ContextClass
is loaded from a jar, then the system will fetch PNGs from within that jar. If it's loaded from a build dir during development/debug, the png is loaded from there. If you've got some fancypants module system that is loading classes straight from the network, then the PNG will also be loaded from there.
resourceKey
A resourcekey is simply a path. It's not really a path, just - a string with slashes. You can't use ..
, for example, it's not really a path. You also, weirdly, can't use filenames that include more than a single dot in the name, for historic (read: silly) reasons.
You have 2 variants - classpackage relative and absolute.
.getResource("/icons/load.png")
is absolute. .getResource("icons/load.png")
is relative. The leading slash is the difference.
If you have:
package com.foo;
public class MyStatusWindow {
...
MyStatusWindow.class.getResource("icons/load.png");
}
And this is all in a jar file (i.e. /com/foo/MyStatusWindow.class
is one of the entries listed if you execute jar tvf myapp.jar
on the command line), then the above would look in that jar for /com/foo/icons/load.png
- the relative form takes the context-class's package and sticks it in front. The absolute form would just look in /icons/load.png
, still in the jar (so it's never C:\
- never the root of your disk - it's the root of the classpath entry).
Maven, Gradle, and just about every other build system has a proscribed directory structure. The above example should go in src/main/java/com/foo/MyStatusWindow.java
, relative to some 'root project dir'. Only java source files are supposed to go there. There's also a resources: src/main/resources/com/foo/icons/load.png
, that's where your icon file would go. Then MyStatusWindow.getResource("icons/load.png")
will just work, in your build system, and in your IDE, and when you ship it all as a jar file. If it doesn't, you've misconfigured your IDE or have a broken build configuration - and you should fix that. Out of the box, this just works.
Upvotes: 2