getClassLoader().getResource(filepath) returns a null pointer - java

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.

Related

What are best practices for coding filepaths in java applications intended for distribution?

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.

AWS Lambda function can't read my .bin files (Java)

I wrote a program in Java and deployed it to AWS Lambda by creating a JAR file containing all the necessary files. My request handler calls a method that tries to create 4 new FileInputStreams, with .bin files as inputs, in a try-catch. For example: InputStream stream = new FileInputStream("random.bin"). However, it's going to the catch statement after trying to create the first one due to an IOException. I have my class files in src/main/java/package. I had the .bin files in the directory that contains the src folder, and I also tried putting them all in one folder, but that didn't resolve the problem. Very confused how to resolve this problem. It's also not working when I call to read in a text file, so it's not just limited to binaries. I was thinking of putting the binaries in my S3 bucket and somehow reading them from there, but I haven't found anything much online detailing how to do that in Java.
Thanks!
Your jar puts files in different locations then what your expect. This question might offer so insight on how to correctly access files in the jar.
AWS Lambda only allow write access in the /tmp folder of your function's filesystem. Try changing your code to match this path and try again.

Are there access right differences between these two ways of creating an InputStream?

I recently dug up some old code for reading data from files. It created an Input stream like this:
InputStream stream =
Thread.currentThread().getContextClassLoader().getResourceAsStream(filepath);
filepath is a String here. This worked fine, for local files, but when trying to read files accessed on a shared network drive, I'd get an error saying the stream was closed.
I created a File object instead, and created the input stream like this instead:
InputStream stream = new FileInputStream(file);
Now it works perfectly. But I got very curious. It was suggested when I had my original line of code, that the file simply did not exist. But switching to the second line of code, it clearly does. Are there any restrictions to access rights with the first approach? What is the difference between them? Why can only the second one access the file?
Assuming you're not doing anything clever with Custom Class loaders, the getResourceAsStream method on the classloader will use the same rules for loading the resource as it does for loading classes: it will only load files present on the classpath.
The fact you're getting null means the file you're loading is not on the classpath. That it's a shared network drive is a red herring.

Loading files with ClassLoader

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);

Getting directory path to .class file containing main

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.

Categories

Resources