This problem has been bugging me for a while. I have to load a couple files in my java app, and the only way I got working so far looks like this:
URL hsURL;
if(System.getProperty("os.name").toLowerCase().contains("windows")) {
hsURL = new URL("file:/" + System.getProperty("user.dir") + "/helpsets/helpset.hs");
}
else {
hsURL = new URL("file://" + System.getProperty("user.dir") + "/helpsets/helpset.hs");
}
But this is ugly and terrible. For a while I thought I had this working:
hsURL = ClassLoader.getSystemResource("helpsets/helpset.hs");
But that no longer works for some reason (I must have changed something and not noticed. It returns null.
Should I be using getResource() instead of getSystemResource() (if so, why is getSystemResource() static but not getResource())?
I am using eclipse and I have tried including the folder in the build path (classpath) and not including it, it doesn't seem to make a difference.
getSystemResource is static because it will use the system classloader, which is available statically. (ClassLoader.getSystemClassLoader)
If your resource is available in the classpath, I would suggest using ClassLoader.getResource() or Class.getResource from an appropriate class, e.g.
Foo.class.getResource("/helpsets/helpset.hs");
(ClassLoader.getResource is "absolute"; Class.getResource is relative to the package of the class unless you prefix it with a '/'.)
If this doesn't work, please post how your app is configured in terms of the classpath, and where your file is.
EDIT: I usually find the URL less useful than an InputStream, so I use getResourceAsStream instead of getResource. YMMV
You've mentioned several different things here, so let's sort them out.
1) Creating a "file:" URL based on "user.dir"
The "user.dir" property refers to the current working directory -- wherever the user might have been when s/he started the app. Chances are good that files written here will disappear between two runs (because the user might run from a different directory).
The "user.home" property refers to the user's home directory -- which should remain the same between runs.
In either case, use a File object to open files, don't muck around with creating a "file:" URL. You get no benefit, and as you can see, you have to write messy code to access it.
2) Retrieving a resource via the classloader
This is meant to retrieve files that are packaged with your application -- read-only files. As you have seen, there are multiple variants. I prefer using the following, because I assume that a class will want to load a file that's packaged with it.
InputStream in = this.getClass().getClassLoader().getResourceAsStream(fileName);
Related
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.
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.
Pick a method
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).
Build systems
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.
Summary: When I load and run one jar from another jar and that loaded jar attempts to access its resources inside itself, it is unable to find those resources. I believe this is because it is looking inside the jar that loaded it instead of itself. How can I fix this?
The problem in more detail:
I am attempting to programmatically load a jar file -- let's call it "Server" -- into the Java Virtual Machine through my own Java program -- let's call it "ServerAPI" -- and use extension and some other tricks to modify the behavior of and interact with Server. ServerAPI depends on Server, but if Server is not present, ServerAPI still has to be able to run and download Server from a website.
To avoid errors caused by ServerAPI loading without satisfying its dependencies from Server, I have made a launcher -- let's call it "Launcher" -- that is intended to download Server and set up ServerAPI as necessary, then load Server and ServerAPI, then run ServerAPI.
I have gotten to the point where Launcher is successfully able to load Server and ServerAPI. However, when I attempt to run Server through ServerAPI, errors appear because Server is unable to locate certain resources. There resources are located inside the Server jar as they always are.
I had the idea that perhaps, since the resource paths all seem to be relative, Java is searching for these resources inside the jar that initially loaded and ran the entire program, i.e. Launcher. To test this, I took some of those missing resources from the Server jar and put them in the same path in the Launcher jar. The errors related to those missing resources disappeared.
So, then, I need a way to ensure that all resources loaded by code in the Server are found in that jar instead of making Java search for them in the Launcher jar. Copying the assets into the Launcher jar worked for my test, but it would be inconvenient and error-prone for the final product, not to mention the fact that it would increase file sizes as there are a large number of resources to copy and that making multiple copies of the same files could lead to errors under certain circumstances.
Ideas that I have already thought of that will not work:
I cannot copy the resources into the Launcher jar because it would increase the size of Launcher; it further complicates the program and can potentially cause errors in cases where sometimes Server is run on its own and sometimes it is run through ServerAPI using Launcher; and it would not allow ServerAPI to be adapted for use with more than one version of Server.
I cannot move all the resources out of Server and into Launcher to eliminate redundancies and ambiguity because for complicated legal reasons, I cannot modify Server. I can, however, modify Launcher or ServerAPI freely, for I created them.
I cannot modify the paths of the resource calls. My tricks for modifying the behavior of Server using ServerAPI will not work in these sections of the code and even if they did, there are so many calls to so many resources that it would take ages to find them all and modify them all.
Research I have already done on this problem:
This question is the only question I've found that seems to be related to the issue, but I cannot modify each call to the resources for the reason mentioned in the paragraph above.
//EDIT:
It seems that Server's resource calls primarily use the format CLASS.class.getResourceAsStream("/RESOURCE"). Does this help at all?
//END EDIT
//EDIT 2:
I tried using Thread.currentThread().setContextClassLoader() to set the ClassLoader to the one I used to load Server, hoping that since I was using the ClassLoader used to load Server that all resource paths would now be relative to Server. Unfortunately, this did not work.
//END EDIT
//EDIT 3:
Up until now, I was initializing the parent of the ClassLoader used to load Server to be Launcher's own ClassLoader, so I just tried setting the parent to null instead to make it act as a standalone ClassLoader, hoping that would break its bond to Launcher. That didn't work either.
//END EDIT
Any help would be greatly appreciated! Thanks in advance!
Through some trickery and hackish fixes, I managed to make it work!
What I did is instead of using the standard URLClassLoader to load the classes in Server and ServerAPI, I used my own custom ClassLoader class that extends URLClassLoader. It worked basically the same way except that I overrode the getResource() method to prepend (attach to the beginning) the absolute path URL of the jar file, basically forcing it to look for the resource in its specific jar file instead of making it relative to the ClassLoader.
The code for that overridden method looks like this:
#Override
public URL getResource(String path) {
/* eliminate the "/" at the beginning if there is one to avoid
conflicts with the "!/" at the end of the URL */
if (path.startsWith("/"))
path.substring(1);
/* prepend "jar:" to indicate that the URL points to a jar file and use
"!/" as the separator to indicate that the resource being loaded is
inside the jar file */
String URL_string = "jar:" + getURLs()[0] + "!/" + path;
try {
return new URL(URL_string);
} catch (MalformedURLException exception) {
System.out.println("There was something wrong with the URL representing this resource!");
System.out.println("URL=\"" + URL_string + "\"");
exception.printStackTrace();
return null;
}
}
I had to learn this the hard way and come up with the fix myself; I hope this answer helps someone else with the same problem!
Note: This also fixes the Class.getResource() method because Class.getResource() simply prepends a "/" if there isn't already one there, then calls ClassLoader.getResource(). It seems to fix Class(Loader).getResourceAsStream() as well, but don't quote me on that one.
How do i access a file that is in the same directory as my source file? I have seen it done in a tutorial and it was extremely simple, but any searches I conduct on the subject are too broad. any help? i.e.
doSomething("file.xml")
How do I access a file relative to the source file i am working with? i haven't seen how to do this, but since it would be an acceptable solution for the first question, here it is: i.e.
doSomething("src/com.package.file.xml")
I really just want a platform independent way to access files in my project. I know its probably a duplicate but please don't hate me.
Generally, you shouldn't
Files stored in the src directory (especially in Eclipse) won't be accessible at when the application is build and deployed.
Netbeans will package these files as part of your Jar when you build it, Eclipse requires you to these files stored in a separate "resources" directory within the project.
At this point, they become known as "embedded resources" and can no longer be accessed like a normal file, but instead, need to be loaded via the resources functionality available in your class.
For example.
To access the resource in com/package/file.xml, you would typically use some thing like...
getClass().getResource("/com/package/file.xml");
This will return a URL which represents the reference to the resource. If it's more confidnent, you can also gain an InputStream directly to the resource using something like...
getClass().getResourceAsStream("/com/package/file.xml");
Which will return an InputStream to the named resource...
This all of course, assumes that the resource can be found ;)
I'm using a method to generate XML files dynamically for a research project, they get put into a loader that reads from a file path, I don't have any control over how the loader handles things (otherwise I'd pass the internal XML representation instead of monkeying with temp files), I'm using this code to save the file:
File outputs = File.createTempFile("lvlFile", ".tmp.xml");
FileWriter fw = new FileWriter(outputs);
fw.write(el.asXML());
fw.close();
// filenames is my list of file paths which gets returned and passed around
filenames.add(outputs.getAbsolutePath());
Now, I'm sure that the file in question is written to directly. If I print outputs.getAbsolutePath() and navigate there via terminal to check the files, everything is generated and written properly, so everything is correct on the filesystem. However, this code:
URL url = this.getClass().getClassLoader().getResource(_levelFile);
Where _levelFile is one of my filenames generated above, causes url to be null. The path isn't getting corrupted or anything, printing verifies that _levelFile points to the correct path. The same code has succeeded for other files. Further, the bug doesn't seem related to whether or not I use getPath(), getCanonicalPath(), or getAbsolutePath(), further setting outputs.isReadable(true) doesn't do anything.
Any ideas? Please don't offer alternatives to the Url url = structure, I don't have any control over this code*, I'm obligated to change my code so that the url is set correctly.
(*) At least without SIGNIFICANT effort rewriting a large section of the framework I'm working with, even though the current code succeeds in all other cases.
Edit:
Again, I can't use an alternative to the URL code, it's part of a loader that I can't touch. Also, the loading fails even if I set the path of the temp file to the same directory that my successfully loaded files come from.
I assume that the ClassLoader will only look for resources within the class path - which probably doesn't include /tmp. I'm not sure if it actually supports absolute path names. It might just interpret them as relative to the root of the individual class path.
How about using _levelFile.toURI().toURL() instead?
Your are creating file in file system and then trying to read it as a resource. Resource is where JVM takes its classes, i.e. the classpath. So this operation will work only if your are writing file into your classpath.
And even if this is correct be careful: if for example you are running from eclipse your process will not probably "see" the new resource until you refresh your workspace.
Now my question is: Are your really sure that you want to read files as resources. It seems that your just should create new FileInputStream(_levelFile) and read from it.
Edit
#Anonymouse is right. You are creating temporary file using 2-arg version of createTempFile(), so your file is created in your temporary directory. The chance that it is into your classpath is very low... :)
So, if you want to read it then you have to get its path or just use it when creating your input stream:
File outputs = File.createTempFile("lvlFile", ".tmp.xml");
..........................
InputStream in = new FileInputStream(ouptuts);
// now read from this stream.
Is it possible to get the path to my .class file containing my main function from within main?
URL main = Main.class.getResource("Main.class");
if (!"file".equalsIgnoreCase(main.getProtocol()))
throw new IllegalStateException("Main class is not stored in a file.");
File path = new File(main.getPath());
Note that most class files are assembled into JAR files so this won't work in every case (hence the IllegalStateException). However, you can locate the JAR that contains the class with this technique, and you can get the content of the class file by substituting a call to getResourceAsStream() in place of getResource(), and that will work whether the class is on the file system or in a JAR.
According to http://www.cs.caltech.edu/courses/cs11/material/java/donnie/java-main.html, no. However, I suggest reading $0 (Program Name) in Java? Discover main class? , which at least gives you the main class .
What do you need it for? If you need it to get hold of any files that are in the same directory, that's what Class.getResourceAsStream() is for.
That looks more like an end-user issue to me. Also consider the possible need to run multiple instances of any given application, and preventing users from doing so is going to become a major annoyance.
If the problem is with temporary file conflicts, then just make sure all your temporary files have unique names. This, as I understand it, is the most common reason people feel a need to prevent multiple instances of their applications from running.
P.S.: The java.io.File.createTempFile() methods are ideally suited for preventing temporary file conflicts because they automatically generate unique filenames.