3

This feels like an absolute newbie question, but I'm on a colossal yak-shave over what should be an elementary concern.

I'm writing a simple desktop app in Java. (Scala actually, but let that pass.) It targets Windows machines. I want to package it and have it runnable without creating an installer, or messing about with the Windows registry. And I want a config file that users can find and edit in order to customize settings. (In the manner of SublimeText's user preferences, for example.)

I envision the following structure:

my-application/
|- bin/
   |- myApp.class (or myApp.jar, if you prefer)
|- files/
   |- config/
      |- config.json
   |- output/
      |- output.xlsx
|-runMyApp.bat

Inside Eclipse, relative paths tend to resolve to the myApplication/ directory, but if I run the app from the command-line, they resolve to whatever the -cp argument was. (Generally, the bin/ directory.)

Am I being naïve? Have I got the wrong paradigm entirely?

It seems to me it would be a fundamental thing to be able to consistently locate files (resources) in the file system surrounding your executables.

Please help!

Leo Orientis
  • 841
  • 11
  • 19
  • Show us your code. – JB Nizet Aug 19 '16 at 14:59
  • Paths are relative to the working directory from where the program is run, not the classpath. – SamTebbs33 Aug 19 '16 at 15:06
  • Why not store config files somewhere inside the user's home directory? It seems to me that most native Windows applications do that (typically either in the `Documents` folder, so that users can find them easily, or in the hidden `AppData` folder, if the users typically don't need to access them directly). – Cyäegha Aug 19 '16 at 15:09
  • if i understand correctly your question , you want to pass a conf file which will be outside of the app jar which means that if the users haven't pay attention to your .readme file, they can put it anywhere . Then why you dont put a simple silly form with a file chooser , and prompt the user at startup , but before your app is completely initialized , to pass you the configuration file. Now you could save the user preferences , and each time before starting you will load that file , or you could prompt each time , and of course your custom handling for validation rules or file not found etc.. – AntJavaDev Aug 19 '16 at 15:18

2 Answers2

8

To package an application means putting the classes in a .jar file, which is a zip file with one or more special Java-specific entries in it. (There are many Stack Overflow answers describing how to do this, such as Java: export to an .jar file in eclipse . Feel free to search for more.)

The problem is, an application cannot write to its own .jar file. On Windows the file is likely to be locked and unwritable; on other systems, it may be possible but is a bad idea for many reasons. You should always consider a .jar file read-only.

Since the configuration needs to be writable, it cannot live in your .jar file, but your .jar file can (and should) include your config.json as a default configuration. The actual configuration should be a file somewhere under the user’s home directory; if it doesn’t exist, your program should copy your default configuration to that location to allow future modification. Alternatively, your program could just ignore missing configuration and default to reading your internally packaged default configuration, but in that case, you’d want to provide thorough documentation of the writable configuration file’s format for end users, since they won’t have an example to go by.

Files packaged inside a .jar file are called resources and are read using the Class.getResource or Class.getResourceAsStream method. You cannot read them using File or Path objects, because they are parts of a zip archive, not actual files. Copying it to a writable location typically looks like this:

Path userConfigFile = /* ... */;

try (InputStream defaultConfig = getClass().getResourceAsStream("/config/config.json")) {
    Files.copy(defaultConfig, userConfigFile);
}

For more information on reading resources, you may want to read about Access to Resources.

On Windows, the actual location of a configuration file is usually in a subdirectory of the user’s AppData directory:

Path configParent;

String appData = System.getEnv("APPDATA");
if (appData != null) {
    configParent = Paths.get(appData);
} else {
    configParent = Paths.get(
        System.getProperty("user.home"), "AppData", "Local");
}

Path configDir = configParent.resolve(applicationName);
Files.createDirectories(configDir);

Path userConfigFile = configDir.resolve("config.json");

If you’re interested in discussion of where to place user configuration files on other platforms, see this question and this one.

VGR
  • 40,506
  • 4
  • 48
  • 63
1

The class File resolves relative paths by default in the user's current working directory (available as system property user.dir). So, if you do cd on command line to change to a directory and then invoke the Java application this directory will be the current user's working directory from the point of view of the Java application.

One thing you can do is to pass your base-directory as system property to the Java application. This can be done within a shell script that starts your application by using it's own location.

Another possibility is to include the files like configuration file in classpath when invoking the Java application and to load them from classpath in Java application. The disadvantage of this solution is that the user can do more things than needed with damage as a possible result.

mm759
  • 1,404
  • 1
  • 9
  • 7
  • Finding out about System Properties, and how to pass them, was the enlightenment I needed. I think I can research the rest. Thank you! – Leo Orientis Aug 19 '16 at 16:08