0

I try to write and read to the file in my java project file called Books.txt. The problem is that I can access the file only if partialPath has full path to the file.

Here is the code:

public <T> List<T> readFromFile(String fileName) {

    private String partialPath = "\\HW3\\src\\java\\repos\\";

    try {
        String path = partialPath + fileName;
        FileInputStream fi = new FileInputStream(path);
        ObjectInputStream oi = new ObjectInputStream(fi);
        // Read objects
        List<T> items = (List<T>) oi.readObject();
        oi.close();
        fi.close();

        return items;
    } catch (IOException | ClassNotFoundException e) {

    }
  }

If I set relative path as above I get exception file not found.

My question is how can I set full path to the current directory programmatically?

Michael
  • 13,950
  • 57
  • 145
  • 288
  • If you want to write to the file, then it can't live in `src` - You should never reference `src` directly, as it won't exists once the project is exported. Instead, you could try placing the file in the current working directory. You can determine the working directory using `System.getProperty("user.dir")`. The problem with this is, it can change, depending on from where the program is executed from. A "more general" approach is to use a "well known" location, such as the user's home directory. You can use `System.getProperty("user.home")` to find that, but each OS has it's own rules – MadProgrammer Dec 29 '18 at 23:44
  • Have a look at [Where should i place my files in order to be able to access them when i run the jar?](https://stackoverflow.com/questions/27974857/where-should-i-place-my-files-in-order-to-be-able-to-access-them-when-i-run-the/27974989#27974989) for some details – MadProgrammer Dec 29 '18 at 23:50
  • Current directory is simply ".". The question is where is the working directory of your project, and this depends a bit on the IDE you are using. This is probably not src but one folder up. If you run the program from command line it will be the same directory from where you start the program. – jbx Dec 30 '18 at 00:25
  • Side comments: use try-with-resources to close your streams. And use the NIO.2 File API to work with files. – Puce Dec 30 '18 at 00:25
  • @MadProgrammer, tnax for post. Can I put the files in project directory? – Michael Dec 30 '18 at 00:43
  • @MadProgrammer, in your reference above mentioned that files can accessed in src files for netbeans editor. – Michael Dec 30 '18 at 00:50
  • @Michael Files stored in the `src` directory become embedded resources, this means that you can no longer treat them as files (as they become an entry in a zip file) and you can no longer write to them and the way you "discover" and read them is different. Since you want to write to the file, I would discourage this direction. Also, at run time, when the project is exported, `src` will no longer exist. You should never reference `src` in your code – MadProgrammer Dec 30 '18 at 01:07
  • 1
    @Michael *"Can I put the files in project directory?"* - You can put the files where ever like, the problem is trying to find them again. There are all sorts of restrictions and issues you need to resolve to make this kind of thing work. This is also a common problem, not just in Java, which has a relatively simple solution - use a "well known location" to store the files, assuming you want read/write access to them – MadProgrammer Dec 30 '18 at 01:12

3 Answers3

1

Here is a code snippet of the Drombler Commons - Client Startup code I wrote, to determine the location of the executable jar. Replace DromblerClientStarter with your main class. This should work at least when you're running your application as an executable JAR file.

/**
 * The jar URI prefix "jar:"
 */
private static final String FULL_JAR_URI_PREFIX = "jar:";
/**
 * Length of the jar URI prefix "jar:"
 */
private static final int FULL_JAR_URI_PREFIX_LENGTH = 4;

private Path determineMainJarPath() throws URISyntaxException {
    Class<DromblerClientStarter> type = DromblerClientStarter.class;
    String jarResourceURIString = type.getResource("/" + type.getName().replace(".", "/") + ".class").toURI().
            toString();
    int endOfJarPathIndex = jarResourceURIString.indexOf("!/");
    String mainJarURIString = endOfJarPathIndex >= 0 ? jarResourceURIString.substring(0, endOfJarPathIndex)
            : jarResourceURIString;
    if (mainJarURIString.startsWith(FULL_JAR_URI_PREFIX)) {
        mainJarURIString = mainJarURIString.substring(FULL_JAR_URI_PREFIX_LENGTH);
    }
    Path mainJarPath = Paths.get(URI.create(mainJarURIString));
    return mainJarPath;
}

Depending on where you bundle Books.txt in your application distribution package, you can use this mainJarPath to determine the path of Books.txt.

Puce
  • 37,247
  • 13
  • 80
  • 152
  • Tnx for post.What is the main class? – Michael Dec 30 '18 at 00:26
  • I am new in java but I have a lot .net background. – Michael Dec 30 '18 at 00:26
  • The main class is the class which has the main method you're calling. – Puce Dec 30 '18 at 00:26
  • 1
    For Java starters I also recommend the Oracle Java tutorials, though they seem only to cover Java 8 and not Java 11: https://docs.oracle.com/javase/tutorial/ – Puce Dec 30 '18 at 00:28
  • 1
    IMHO this seems like taking sledge hammer to crack a pea-nut. A simpler solution is simple trying to resolve a "well known" location for the file to reside which is independent of the location that the Jar file resides or from where it is executed, which is a common enough problem and solution pattern already in use - just saying – MadProgrammer Dec 30 '18 at 01:09
  • @MadProgrammer It depends on the nature of the file. If it is a pre-configured, but editable file you want to ship with your product, then I think this is a valid solution. But on second thought, I think you're right in this case. Books.txt at least sounds like a user file, so it should be somewhere in the user data space and not where the application is installed. – Puce Dec 30 '18 at 10:03
  • @puce Not really, it’s easy to “install” the file to a “well known location” in any number of ways, from writing an initial file, copying it from the Jar file itself or just using an installer. The problem isn’t unique to Java and has been “solved” and well documented if you’re willing to read the relevant OS API documents - just saying – MadProgrammer Dec 30 '18 at 11:31
  • @MadProgrammer You mention some possible solutions, which are fine for some kind of files. – Puce Dec 30 '18 at 14:03
  • @pruce While I accept that there is always an exception to the rule, in the vast majority of the cases, where you want writability, a "well known location" is the best solution for a number of reasons. If you just want a readable file, then embedded as a location resource within the Jar it self – MadProgrammer Dec 30 '18 at 22:03
0

I also feel that files created (and later possibly modified and or deleted) by your running Java application is usually better to be placed in a location of the file system that is away from your java application installed home directory. An example might be the 'C:\ProgramData\ApplicationNameFiles\' for the Windows operating system or something similar for other OS platforms. In my opinion, at least for me, I feel it provides less chance of corruption to essential application files due to a poorly maintained drive or, accidental deletion by a User that opens up a File Explorer and decides to take it upon him/her self to clean their system of so called unnecessary files, and other not so obvious reasons.

Because Java can run on almost any platform and such data file locations are platform specific the User should be allowed to select the location to where these files can be created and manipulated from. This location then can be saved as a Property. Indeed, slightly more work but IMHO I feel it may be well worth it.

It is obviously much easier to create a directory (folder) within the install home directory of your JAR file when it's first started and then store and manipulate your application's created data files from there. Definitely much easier to find but then again...that would be a matter of opinion and it wouldn't be mine. Never-the-less if you're bent on doing it this way then your Java application's Install Utility should definitely know where that install path would be, it is therefore just a matter of storing that location somewhere.

No Install Utility? Well then your Java application will definitely need a means to know from where your JAR file is running from and the following code is one way to do that:

public String applicationPath(Class mainStartupClassName) {
    try {
        String path = mainStartupClassName.getProtectionDomain().getCodeSource().getLocation().getPath();
        String pathDecoded = URLDecoder.decode(path, "UTF-8");
        pathDecoded = pathDecoded.trim().replace("/", File.separator);
        if (pathDecoded.startsWith(File.separator)) {
            pathDecoded = pathDecoded.substring(1);
        }
        return pathDecoded;
    }
    catch (UnsupportedEncodingException ex) {
        Logger.getLogger("applicationPath() Method").log(Level.SEVERE, null, ex);
    }
    return null;
}

And here is how you would use this method:

String appPath = applicationPath(MyMainStartupClassName.class);

Do keep in mind that if this method is run from within your IDE it will most likely not return the path to your JAR file but instead point to a folder where your classes are stored for the application build.

DevilsHnd - 退職した
  • 8,739
  • 2
  • 19
  • 22
0

This is not a unique issue to Java, it's a problem faced by any developer of any language wishing to write data locally to the disk. The are many parts to this problem.

If you want to be able to write to the file (and presumably, read the changes), then you need to devise a solution which allows you find the file in a platform independent way.

Some of the issues

The installation location of the program

While most OS's do have some conventions governing this, this doesn't mean they are always used, for what ever reason.

Also, on some OS's, you are actively restricted from writing to the "installation" location. Windows 8+ doesn't allow you to write to the "Program Files" directory, and in Java, this usually (or at least when I was dealing with it) fails silently.

On MacOS, if you're using a "app bundle", the working directory is automatically set to the user's home directory, making it even more difficult to manage

The execution context (or working directory) may be different from the installation location of the program

A program can be installed in one location, but executed from a different location, this will change the working directory location. Many command line tools suffer from this issue and use different conventions to work around it (ever wonder what the JAVA_HOME environment variable is for )

Restricted disk access

Many OS's are now actively locking down the locations to which programs can write, even with admin privileges.

A reusable solution...

Most OS's have come up with conventions for solving this issue, not just for Java, but for all developers wishing to work on the platform.

Important Like all guide lines, these are not hard and fast rules, but a recommendations made by the platform authors, which are intended to make your life simpler and make the operation of the platform safer

The most common solution is to simply place the file in a "well known location" on the disk, which can be accessed through an absolute path independently of the installation or execution location of the program.

On Windows, this means placing the file in either ~\AppData\Local\{application name} or ~\AppData\Roaming\{application name}

On MacOS, this means placing the file in ~/Library/Application Data/{application name}

On *nix, this typically means placing the file in ~/.{application name}

It could be argued that you could use ~/.{application name} on all three platforms, but as a user who "shows hidden files", I'd prefer you didn't pollute my home directory.

A possible, reusable, solution...

When Windows 8 came out, I hit the "you can't write to the Program Files" issue, which took some time to diagnose, as it didn't generate an exception, it just failed.

I was also working a lot more on Mac OS as well, so I needed a simple, cross platform solution, so my code could automatically adapt without the need for multiple branches per platform.

To this end, I came with a simple utility class...

public enum SystemUtilities {
    
    INSTANCE;

    public boolean isMacOS() {
        return getOSName().startsWith("Mac");
    }

    public boolean isMacOSX() {
        return getOSName().startsWith("Mac OS X");
    }

    public boolean isWindowsOS() {
        return getOSName().startsWith("Windows");
    }
    
    public boolean isLinux() {
        return getOSName().startsWith("Linux");
    }

    public String getOSName() {
        return System.getProperty("os.name");
    }

    public File getRoamingApplicationSupportPath() {
        // For *inx, use '~/.{AppName}'
        String path = System.getProperty("user.home");
        if (isWindowsOS()) {
            path += "\\AppData\\Roaming";
        } else if (isMacOS()) {
            path += "/Library/Application Support";
        }
        return new File(path);
    }

    public File getLocalApplicationSupportPath() {
        // For *inx, use '~/.{AppName}'
        String path = System.getProperty("user.home");
        if (isWindowsOS()) {
            path += "\\AppData\\Local";
        } else if (isMacOS()) {
            path += "/Library/Application Support";
        }
        return new File(path);
    }

}

This provides a baseline from which "independent" code can be built, for example, you could use something like...

File appDataDir = new File(SystemUtilities.INSTANCE.getLocalApplicationSupportPath(), "MyAwesomeApp");
if (appDataDir.exists() || appDataDir.mkdirs()) {
    File fileToWrite = new File(appDataDir, "Books.txt");
    //...
}

to read/write to the file. Although, personally, I might have manager/factory do this work and return the reference to the end File, but that's me.

What about "pre-packaged" files?

Three possible solutions...

  1. Create the file(s) if they don't exist, populating them with default values as required
  2. Copy "template" file(s) out of the Jar file, if they don't exist
  3. Use an installer to install the files - this is the solution we used when we were faced with changing the location of all our "external" configuration files.

Read only files...

For read only files, the simplest solution is to embedded them within the Jar as "embedded resources", this makes it easier to locate and manage...

URL url = getClass().getResource("/path/to/readOnlyResource.txt");

How you do this, will depend on your build system

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366