Java - Cross-platform filepath - java

I'm trying to develop a cross-platform application that works on Desktop and Android as well using JavaFX and Gluon.
At runtime my code creates a serialized file in my resource folder. I also need to read and write serialized data from/to it.
I managed to work it on desktop, but not on android. Because it have a different file structure I guess.
That is why I try to get the file path dynamically.
Existing resource files, which are created before runtime (and not modified) seems to works fine on both platform.
I tried with new File("src/main/resources/folder/file.ser").getAbsolutePath(); and by trying to access it from my root folder like this: getClass.getResources("/folder/file.ser").getPath();. Both of them works fine on desktop (Windows) but unfortunately Android does not find the file by file path.
An other problem could be that I should not create runtime files in the resource folder but then where should I?
Any idea how can I read and write runtime created files that works both on android and desktop?
(If the information is not enough to help me, I try to reproduce my code in a minimal form and provide further details.)

I think you are on a completely wrong track. Creating or writing to files in the resource folder does not work in general. The idea is that files in the resource folder get packaged into jar files or are otherwise bundled with an application and are not writable at runtime.
What you should do is to create an application folder when your program is launched for the first time. A common practice on desktop is for example to create an invisible folder ".myApp" in the users home directory. On other platforms like Android there are other platform specific naming and location rules, but the concept is the same. At first launch time you can also copy necessary resources from your resource folder into this application folder so that you can edit them at runtime.

Resource files with a path on the class path, could be packed in a jar and should be considered read-only, especially as resources might be cached in some cases. They are not File. They can be captured by URL, URI, Path. The paths are case-sensitive and the path separator is /.
Hence resources can only be used as a template, an initial file. They should be copied to a real File, outside the application.
Path path = Paths.get(System.getProperty("user.home"), ".myapp/file.ser");
Files.createDirectories(path.getParent());
if (Files.exists(path)) {
URL url = MyClass.class.getResource("/folder/file.ser");
Path template = Paths.get(url.toURI());
Files.copy(template, path);
}
Furthermore .ser, a serialized java object, is not a good idea. I would suggest XML
using JAXB with annotations. More readable, maintainable, versionable. No clash between development JRE at your place and deployed JRE at the client.

Related

Problems with JPackage due to different path structure between Windows and Linux

On Windows I have just converted my application installer from Izpack to JPackage and because of the structure created by JPackage I had to make some code changes to allow my application to find various config files, basically they are copied from app folder to C:\Users\Username\Appdata.... on first start (but bit more complicated than that).
I am now using JPackage on Linux, and the application copies config files from app to $HOME/.appname. However whereas on Windows app is a subfolder relative to launcher with Linux its now in ../lib/app so the code doesnt work
So I have to make some linux specific changes, but it got me wondering am I doing something very wrong here in order for me to have to make these changes ?
One way to load configuration files is using the classloader: the files are packaged in a jar along with the rest of your application and you load them as a stream, not as a file:
Properties properties = new Properties();
InputStream stream = YourApplication.class.getResourceAsStream("/resources/conf.properties");
properties.load(stream);
The path (example) /resources/conf.properties is relative the .jar file where the file is bundled, so it does no depend on the installation folder.
The above example assumes that you're using a Properties file but this works for any resource you can load as a stream.

How is current working directory determined when running from Eclipse? [duplicate]

I was trying to load a file in a webapp, and I was getting a FileNotFound exception when I used FileInputStream. However, using the same path, I was able to load the file when I did getResourceAsStream().
What is the difference between the two methods, and why does one work while the other doesn't?
The java.io.File and consorts acts on the local disk file system. The root cause of your problem is that relative paths in java.io are dependent on the current working directory. I.e. the directory from which the JVM (in your case: the webserver's one) is started. This may for example be C:\Tomcat\bin or something entirely different, but thus not C:\Tomcat\webapps\contextname or whatever you'd expect it to be. In a normal Eclipse project, that would be C:\Eclipse\workspace\projectname. You can learn about the current working directory the following way:
System.out.println(new File(".").getAbsolutePath());
However, the working directory is in no way programmatically controllable. You should really prefer using absolute paths in the File API instead of relative paths. E.g. C:\full\path\to\file.ext.
You don't want to hardcode or guess the absolute path in Java (web)applications. That's only portability trouble (i.e. it runs in system X, but not in system Y). The normal practice is to place those kind of resources in the classpath, or to add its full path to the classpath (in an IDE like Eclipse that's the src folder and the "build path" respectively). This way you can grab them with help of the ClassLoader by ClassLoader#getResource() or ClassLoader#getResourceAsStream(). It is able to locate files relative to the "root" of the classpath, as you by coincidence figured out. In webapplications (or any other application which uses multiple classloaders) it's recommend to use the ClassLoader as returned by Thread.currentThread().getContextClassLoader() for this so you can look "outside" the webapp context as well.
Another alternative in webapps is the ServletContext#getResource() and its counterpart ServletContext#getResourceAsStream(). It is able to access files located in the public web folder of the webapp project, including the /WEB-INF folder. The ServletContext is available in servlets by the inherited getServletContext() method, you can call it as-is.
See also:
Where to place and how to read configuration resource files in servlet based application?
What does servletcontext.getRealPath("/") mean and when should I use it
Recommended way to save uploaded files in a servlet application
How to save generated file temporarily in servlet based web application
getResourceAsStream is the right way to do it for web apps (as you already learned).
The reason is that reading from the file system cannot work if you package your web app in a WAR. This is the proper way to package a web app. It's portable that way, because you aren't dependent on an absolute file path or the location where your app server is installed.
FileInputStream will load a the file path you pass to the constructor as relative from the working directory of the Java process. Usually in a web container, this is something like the bin folder.
getResourceAsStream() will load a file path relative from your application's classpath.
The FileInputStream class works directly with the underlying file system. If the file in question is not physically present there, it will fail to open it. The getResourceAsStream() method works differently. It tries to locate and load the resource using the ClassLoader of the class it is called on. This enables it to find, for example, resources embedded into jar files.
classname.getResourceAsStream() loads a file via the classloader of classname. If the class came from a jar file, that is where the resource will be loaded from.
FileInputStream is used to read a file from the filesystem.
I am here by separating both the usages by marking them as File Read(java.io) and Resource Read(ClassLoader.getResourceAsStream()).
File Read -
1. Works on local file system.
2. Tries to locate the file requested from current JVM launched directory as root
3. Ideally good when using files for processing in a pre-determined location like,/dev/files or C:\Data.
Resource Read -
1. Works on class path
2. Tries to locate the file/resource in current or parent classloader classpath.
3. Ideally good when trying to load files from packaged files like war or jar.

How to access Jar file located within source folders?

in my Java project I am using an H2 in-memory database, for which I have to load the JDBC driver when I initialize my application. I want/need to load the H2 .jar file dynamically, so I do the following:
String classname = "org.h2.Driver";
URL u = new URL("jar:file:libs/h2.jar!/");
URLClassLoader ucl = new URLClassLoader(new URL[] { u });
Driver d = (Driver) Class.forName(classname, true, ucl).newInstance();
DriverManager.registerDriver(new DriverShim(d));
When I put the H2 .jar file into a "libs" folder outside my Java source code folder (that is, in Eclipse, this "libs" directory is on the same level as the "src" folder), then this approach works fine. However, unfortunately I have to put this H2 .jar file into a folder within the source code folder tree, but below the main class folder.
For example, my Java package structure looks like this in Eclipse:
<project>/src/my/app/MyApp.java // main class of my application
<project>/src/my/app/sub/package/h2.jar // how to access this?
<project>/libs/h2.jar // loading from here works
I know this is stupid, but unfortunately I have to work with this strange setup. But what I don't know: how can I edit my Java code (listed above) in order to work with this setup?
EDIT: This has to work outside Eclipse as well, so adding the JAR file to the Java Build Path in Eclipse is no option for me.
EDIT2: I already tried to load "jar:file:my/app/sub/package/h2.jar!/", but that did not work for me.
Thanks in advance for all helpful ideas!
Kind regards, Matthias
In some frameworks referring to files inside JARs can be done using the classpath: prefix. I doubt URLClassLoader supports it natively, but it's worth a try (e.g. classpath:/my/app/sub/package/h2.jar). But since that doesn't work with URLClassLoader, here are other ways:
One way to do it would be to write your own ClassLoader which reads the JAR file from classpath (using getResourceAsStream), uncompresses it (using ZipInputStream) to memory (e.g. a map of byte arrays) and loads the classes from there.
Another, slightly easier way, is to read the JAR file from classpath and write it into a temporary file. Then you can use the plain URLClassLoader to load classes from it. This has the disadvantage that the file must be written to a file and the file probably cannot be removed until the JVM exits (unless using Java 7 or higher).
I'm using the second approach (copying to a temp file) in one project, though I'm using it to launch an external process. I would be curious to hear why you have such a requirement. If it's just a matter of having the whole application in one JAR, there are numerous simpler methods for achieving that (Maven Assembly Plugin, Maven Shade Plugin, Jar Jar Links, One-JAR to name a few).
No it's not a homework, but an online build system that uses my classes under my/app/* and several other classes (not from me) to automatically build the whole solution. Anyway, I can't give you more details on the internals of this system, as I don't know them. As said, I simply have to live with it, and that is why I am asking here...
Sounds like you are working in a WTF environment (does it have a name?), so here are some ways to start hacking around it:
Find out more about your environment, especially absolute file paths of the following: directory where the source files are saved, directory where the generated .class files are saved, and the current working directory when the program is run.
If you can get any kind of output of what your program prints during runtime, you can put into your application some debug code where you use File.listFiles() to crawl the machine's directory trees. If you can get output only from what happens when compiling, it might be possible to execute your own code during compile by creating your own annotation processor (apt is part of javac since Java 6), though I'm not sure whether the annotation processor must be compiled first separately.
The working directory can be read from the user.dir system property and the location of class files can be probably gotten from the java.class.path system property (unless custom class loaders are used). There is no guarantee that a JAR file in the source directory would be copied to the classpath, so you might need to do some looking around.
Then when you know the file path of the JAR file, then you can get an URL to it using new File("path/to/h2.jar").toURI().toURL() which you can then pass to URLClassLoader.
If nothing else works, upload the source code of the libraries and compile them together with your project.
In the long run, try to replace the WTF build environment with one that uses a standard build tool (such as Maven) and a common CI server (such as Jenkins). It's normal for projects to have lots of library dependencies, so you shouldn't need to hack around a build environment to use them.

getResourceAsStream() vs FileInputStream

I was trying to load a file in a webapp, and I was getting a FileNotFound exception when I used FileInputStream. However, using the same path, I was able to load the file when I did getResourceAsStream().
What is the difference between the two methods, and why does one work while the other doesn't?
The java.io.File and consorts acts on the local disk file system. The root cause of your problem is that relative paths in java.io are dependent on the current working directory. I.e. the directory from which the JVM (in your case: the webserver's one) is started. This may for example be C:\Tomcat\bin or something entirely different, but thus not C:\Tomcat\webapps\contextname or whatever you'd expect it to be. In a normal Eclipse project, that would be C:\Eclipse\workspace\projectname. You can learn about the current working directory the following way:
System.out.println(new File(".").getAbsolutePath());
However, the working directory is in no way programmatically controllable. You should really prefer using absolute paths in the File API instead of relative paths. E.g. C:\full\path\to\file.ext.
You don't want to hardcode or guess the absolute path in Java (web)applications. That's only portability trouble (i.e. it runs in system X, but not in system Y). The normal practice is to place those kind of resources in the classpath, or to add its full path to the classpath (in an IDE like Eclipse that's the src folder and the "build path" respectively). This way you can grab them with help of the ClassLoader by ClassLoader#getResource() or ClassLoader#getResourceAsStream(). It is able to locate files relative to the "root" of the classpath, as you by coincidence figured out. In webapplications (or any other application which uses multiple classloaders) it's recommend to use the ClassLoader as returned by Thread.currentThread().getContextClassLoader() for this so you can look "outside" the webapp context as well.
Another alternative in webapps is the ServletContext#getResource() and its counterpart ServletContext#getResourceAsStream(). It is able to access files located in the public web folder of the webapp project, including the /WEB-INF folder. The ServletContext is available in servlets by the inherited getServletContext() method, you can call it as-is.
See also:
Where to place and how to read configuration resource files in servlet based application?
What does servletcontext.getRealPath("/") mean and when should I use it
Recommended way to save uploaded files in a servlet application
How to save generated file temporarily in servlet based web application
getResourceAsStream is the right way to do it for web apps (as you already learned).
The reason is that reading from the file system cannot work if you package your web app in a WAR. This is the proper way to package a web app. It's portable that way, because you aren't dependent on an absolute file path or the location where your app server is installed.
FileInputStream will load a the file path you pass to the constructor as relative from the working directory of the Java process. Usually in a web container, this is something like the bin folder.
getResourceAsStream() will load a file path relative from your application's classpath.
The FileInputStream class works directly with the underlying file system. If the file in question is not physically present there, it will fail to open it. The getResourceAsStream() method works differently. It tries to locate and load the resource using the ClassLoader of the class it is called on. This enables it to find, for example, resources embedded into jar files.
classname.getResourceAsStream() loads a file via the classloader of classname. If the class came from a jar file, that is where the resource will be loaded from.
FileInputStream is used to read a file from the filesystem.
I am here by separating both the usages by marking them as File Read(java.io) and Resource Read(ClassLoader.getResourceAsStream()).
File Read -
1. Works on local file system.
2. Tries to locate the file requested from current JVM launched directory as root
3. Ideally good when using files for processing in a pre-determined location like,/dev/files or C:\Data.
Resource Read -
1. Works on class path
2. Tries to locate the file/resource in current or parent classloader classpath.
3. Ideally good when trying to load files from packaged files like war or jar.

How do you get file path for file when user directory changes?

I have a java app, and the log4j.properties file is in src/com/my/path/props. On compile, it's copied into classes/com/my/path/props
The file is loaded via PropertyConfigurator.configureAndWatch(user.dir + "/classes/com/my/path/props/log4j.properties").
This all works fine normally, though it's not ideal because of using user.dir (but I do not know another way to reference a file relative to the "application's start directory"). The problem manifests when trying to run this application using an NT Service wrapper. When done this way, the user.dir changes from the application's root dir to wherever the NTService wrapper's exe file is.
My question is: What's the appropriate way to get a the String file path representation of the log4j.properties file in my classes/com/my/path/props/ directory? I realize this would completely break down if the props file were in a jar; but in this case, it's not and is simply a file on the file system.
I've tried new File(this.getClass().getClassLoader().getResource("com/my/path/props/log4j.properties").getURI()).getAbsolutePath(), but that fails because on production, the path to the file is actually a UNC path and consequently throws a "URI has an authority component" exception.
How do other people deal with this problem?
Thanks.
OK. So... you asked how other people deal with this problem. First, they do not leave it up to Eclipse for where files get placed. They choose where they want them, how they want to access them, and then have their build tool (which unless they are just playing around, should not be an IDE like Eclipse, but rather a dedicated build tool like Maven or Ant) where to place it.
The choice of where you want the file depends on what you want to do with it. If its simply a config file that will never be edited at runtime, you typically place it inside your JAR (which is another practice - applications are placed in one or more JARs, WARs, or EARs, not a classes directory). If the file is to be edited at runtime, which from your "watching" it appears to be the case, you typically put it in a config directory outside your JAR.
How you access it (from the filepath or the classpath) is another choice. Where possible, I favor accessing files from the classpath because it is more portable - and when in a JAR, pretty much required. If that doesn't make sense in your case, then choose a path other than "user.dir" if that is changing when you deploy. You can hard-code it, use an environment variable, a property, a config file, a command line argument, etc. to set the actual path.
Always choose where things go and how you access them. Don't let your tools choose for you. It will make your life easier :-)
I took singleshot's advice and kept the properties files out of src and instead in a separate directory which I added to the classpath. In retrospect, this was indeed boneheaded to have configured it the way I did originally.
From there, my problem was getting a File from a URL. I ended up finding what I needed in Commons IO FileUtils, with its toFile(URL) method.
The code ended up looking like this:
private URL maintenanceConfigPath = this.getClass().getClassLoader().getResource("MaintenanceConfig.properties");
....
File f = FileUtils.toFile(maintenanceConfigPath);
....
Again, thanks to all for your feedback and for pointing me down a path that got me towards an answer

Categories

Resources