0

When someone opens my jar up I am opening a file selector gui so they can choose where they want to store their jar's files like config files and such. This should only take place the first time they open the jar. However, one issue with this approach is that I would have no way to know if it's their first time opening the jar since I will need to save the selected path somewhere. The best solution to this sounds like saving the selected path inside a file in the resource folder which is what I am having issues with. Reading and writing to this resource file will only need to be done when the program is actually running. These read and write operations need to work for packaged jar files (I use maven) and in the IDE.

I am able to read a resources file inside of the IDE and then save that file to the designated location specified in the file selector by doing this. However, I have not been able to do the same from a jar despite trying multiple other approaches from other threads.

        ClassLoader classloader = Thread.currentThread().getContextClassLoader();
        InputStream is = classloader.getResourceAsStream("config.yml");
        try {
            if(is != null) {
                Files.copy(is, testFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
                is.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

So just to clarify when my project is loaded I need to listen for the user to select a valid path for files like my config. Then I want to write my config to that path which I can do from my IDE and is shown above but I cant figure this out when I compile my project into a jar file since I always receive a file not found error in my cmd. However, the main point of this post is so that I can figure out how to save that selected path to my resource folder to a file (it can be json, yml or whatever u like). In the code above I was able to read a file but I have no idea how to go from that to get the files path since then reading and writing to it would be trivial. Also keep in mind I need to be able to read and write to a resource folder from both my IDE and from a compiled jar.

The following code shows my attempt at reading a resource from a compiled jar. When I added a print statement above name.startWith(path) I generated a massive list of classes that reference config.yml but I am not sure which one I need. I assume it has to be one of the paths relating to my project or possible the META-INF or META-INF/MANIFEST.MF path. Either way how am I able to copy the file or copy the contents of the file?

  final String path = "resources/config.yml";
  final File jarFile = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath());

    if(jarFile.isFile()) {  // Run with JAR file
        try {
            final JarFile jar = new JarFile(jarFile);
            final Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
            while(entries.hasMoreElements()) {
                final String name = entries.nextElement().getName();
                if (name.startsWith(path)) { //filter according to the path
                    System.out.println(name);
                }
            }
            jar.close();
        } catch (IOException exception) {
            exception.printStackTrace();
        }
    }

Also if you were wondering I got the above code from the following post and my first block of code I pasted above is actually in the else statement since the IDE code from that post also did not work. How can I access a folder inside of a resource folder from inside my jar File?

3 Answers3

1

You can't write to files inside your JAR file, because they aren't actually files, they are ZIP entries.

The easiest way to store configuration for a Java application is to use Preferences:

Preferences prefs = Preferences.userNodeForPackage(MyApp.class);

Now all you have to do is use any of the get methods to read, and put methods to write.

Rob Spoor
  • 6,186
  • 1
  • 19
  • 20
  • Oh I never knew about that thank you!! However I will still need to be able to read files that are in the resources folder from jars and the IDE since I will need to copy those files over. – IDontWantToMakeAnotherAccount Sep 12 '22 at 20:19
  • The preferences API (https://docs.oracle.com/en/java/javase/17/docs/api/java.prefs/java/util/prefs/Preferences.html) allows you to store simple values, not whole files. From what you mention here, you might be better off by creating a 'data directory' underneath the user's home. – Queeg Sep 12 '22 at 20:26
  • @HiranChaudhuri Is there a way to view the saved data inside the Preference class? All I need to save is a single string containing a path to the config and such files directory. Plus I am not exactly sure what the other answer was even about. – IDontWantToMakeAnotherAccount Sep 12 '22 at 20:39
  • This is implementation specific. For Oracle Java on Windows, the preferences end up in the Windows registry. On Linux, they end up in some XML file. I never investigated the details. – Queeg Sep 12 '22 at 20:41
  • @HiranChaudhuri That makes sense but where would it be located inside of my project folder? For example on Windows I can view the src folder which contains main then java and the resource folders but where would presences be saved at? – IDontWantToMakeAnotherAccount Sep 12 '22 at 20:43
  • Now that we are talking Windows Registry, check out https://en.wikipedia.org/wiki/Windows_Registry#File_locations: the user-specific HKEY_CURRENT_USER user registry hive is stored in Ntuser.dat inside the user profile. BTW, I found this for Linux: https://stackoverflow.com/a/23961144/4222206 – Queeg Sep 12 '22 at 20:46
  • @HiranChaudhuri Alright thanks also I updated my question (specifically the paragraph above my last code snippet). In summery I was able to get a list of paths from a running jar but I am not sure the best one to choose and how to go from that to the actual file. – IDontWantToMakeAnotherAccount Sep 12 '22 at 21:37
  • If you have a file you know, you can use `getResource()` to get a URL. There's an `!` somewhere, e.g. `jar:file:/path/to/my-file.jar!/resource/path`. Strip away everything from that `!`, then create a `URI` from the result. Then you can use `FileSystems.newFileSystem(uri, Map.of())` to create a file system to that JAR. Note that this only works when working with a JAR; when working from source you can use the default file system. – Rob Spoor Sep 13 '22 at 08:14
  • Interesting trick - class files are resources too! You can use `MyClass.getResource("MyClass.class")` to get a known resource if no other files are available. – Rob Spoor Sep 13 '22 at 08:15
0

There is absolutely no need to write files into your resource folder inside a jar. All you need to have is a smart classloader structure. Either there is a classloader that allows changing jars (how difficult is that to implement?), or you just setup a classpath that contains an empty directory before listing all the involved jars.

As soon as you want to change a resource, just store a file in that directory. When loading the resource next time, it will be searched on the classpath and the first match is returned - in that case your changed file.

Now it could be that your application needs to be started with that modified classpath, and on top of that it needs to be aware which directory was placed first. You could still setup that classloader structure within a launcher application, which then transfers control to the real application loaded via the newly defined classloader.

Queeg
  • 7,748
  • 1
  • 16
  • 42
  • What does one get from manually loading classes? Anyways as the first answer suggested I can save the config files and other relevant data to the specified path. The one issue is I need to be able to read files from the resource folder (I just dont need to write to them any more). Since I already got it working in with the IDE I just need to figure out how to read files in the resource folder from jars so I can copy them over to the specified path. – IDontWantToMakeAnotherAccount Sep 12 '22 at 20:28
  • In this answer you can see how to load an image from a resource inside the jar: https://stackoverflow.com/a/72310825/4222206 – Queeg Sep 12 '22 at 20:31
  • Are you sure that is for jars because it looks like the same sort of thing I tried when working with in an IDE. Look at the linked post I provided in my original question. – IDontWantToMakeAnotherAccount Sep 12 '22 at 20:41
  • To be more concise, it loads from the classpath. So it depends on how your classpath is setup. IDEs typically run from the compiled classes and the resource directory directly to save the effort of packaging. But when the build process completes, the packaging will be done and after that the classpath will be setup to run from a jar. – Queeg Sep 12 '22 at 20:52
  • Alright I found the presences and it's actually stored in registry editor (on windows). But I still need to figure out how to retrieve files from the resource folder from inside a compiled jar. What problems does packaging after classpath is set up cause then? – IDontWantToMakeAnotherAccount Sep 12 '22 at 21:08
0

You could also check on application startup whether a directory such as ${user.home}/${application_name}/data exists.

If not, create it by extracting a predefined zip into this location. Then just run your application which would load/write all the data in this directory.

No need to read/write to the classpath. No need to include 3rd party APIs. And modifying this initial data set would just mean to distribute a new zip to be extracted from.

Queeg
  • 7,748
  • 1
  • 16
  • 42