0

I have a fairly simple Java project that opens (creates if doesn't exist) a text file in the current directory (somewhere within my Documents folder), reads the data with BufferedReader, then modifies with PrintWriter. The code works fine in Eclipse. But when exported to a runnable .jar file the resulting .jar executable can only modify a file that it itself created (created because it didn't exist before). If I then close and re-launch the .jar as a new instance to modify the file it created last launch I get

java.io.FileNotFoundException: Data.txt (Access is denied)
    at java.base/java.io.FileOutputStream.open0(Native Method)
    at java.base/java.io.FileOutputStream.open(FileOutputStream.java:293)
    at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:235)
    at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:184)
    at java.base/java.io.PrintWriter.<init>(PrintWriter.java:309)
    at project.file.LocalStorageFile.setList(LocalStorageFile.java:136)
    ...

So to reiterate:

  1. Run in eclipse - works fine
  2. Export to runnable jar
  3. Run jar
  4. If file doesn't exist it creates one
  5. Reads file
  6. if it created the file in step 4 - writes successfully, but if file already existed - exception

When looking into this exception the answers suggest:

  • restricted directory on disk C (which can't be it for me, as mine is a perfectly accessible (at least once) folder in Documents, and the folder's permissions seem to be in order)
  • forgetting to close streams (I close both reader and writer streams in a finally block and make sure they were closed with some console print lines)
  • file is being used (the problem persists after a computer restart and even after an OS re-installation I had to do earlier)
  • lack of admin rights (I've launched CMD as administrator and used it to "java -jar project.jar" with same results)
  • tried deleting file using file.delete() which equals false
  • tried flushing the print writer
  • tried setting file to readable and writable with file.setReadable(true) and file.setWritable(true)

I am using gradle to build my .jar because I need some libs I get from there for other functions. but even if I "export as runnable jar" using Eclipse itself I get the same issue. And of course when I run the program directly from Eclipse multiple times I get no problem accessing and modifying the same file.

Here are is my LocalStorageFile class with the read and write functions if you want to test it on your machine:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.TreeMap;

/**
 * Store keywords and strings in a local file.
 * 
 * Format: keyword1, keyword2 \t string1 \n keyword3, keyword4 \t string2
 * \n
 */
public class LocalStorageFile {
    private static final String FILE_PATH = "Data.txt"; // current directory
    private static final String KEY_SEP = "\t"; // key/value separator
    private static final String LINE_SEP = "\n"; // line separator

    private static File file;

    public LocalStorageFile() {
        file = new File(FILE_PATH);

        // Create file if it doesn't exist
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Retrieve list from file "Data.txt" in local directory.
     * 
     * @return TreeMap of keys and strings.
     */
    public static TreeMap<String, String> getList() {
        System.out.println("Retrieving list data.");
        TreeMap<String, String> myList = new TreeMap<String, String>();

        File file = new File(FILE_PATH);
        if (!file.exists()) {
            try {
                file.createNewFile(); // if file already exists will do nothing
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }

        BufferedReader br = null;
        // Read file line by line and add to myList
        try {
            file.setReadable(true);
            file.setWritable(true);
            br = new BufferedReader(new FileReader(file));
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println("  Now reading line:" + line);
                String[] keyValuePair = line.split(KEY_SEP);
                if (keyValuePair.length == 2) { // avoid any error lines
                    // Re-insert tabs and newlines
                    String key = keyValuePair[0].replaceAll("\\\\t", "\t")
                            .replaceAll("\\\\n", "\n");
                    String value = keyValuePair[1].replaceAll("\\\\t", "\t")
                            .replaceAll("\\\\n", "\n");
                    // Put data into map
                    myList.put(key, value);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null) {
                    br.close();
                    System.out.println("Buffered reader closed.");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return myList;
    }

    /**
     * Rewrite list to file "Data.txt" in local directory.
     * 
     * @param myList
     *            TreeMap of keys and strings.
     * @return 1 on success, 0 on fail.
     */
    public static int setList(TreeMap<String, String> myList) {
        System.out.println("Saving list data.");
        String textData = "";
        int result = 0;

        // Construct textData using myList
        for (String key : myList.keySet()) {
            String value = myList.get(key);

            // Sanitize strings
            String keyClean = key.replaceAll("\t", "\\\\t").replaceAll("\n",
                    "\\\\n");
            String valueClean = value.replaceAll("\t", "\\\\t").replaceAll(
                    "\n", "\\\\n");

            // Assemble line with separators
            String line = keyClean + KEY_SEP + valueClean + LINE_SEP;

            System.out.println("  Now saving line:" + line);
            textData += line;
        }

        // Replace file content with textData
        PrintWriter prw = null;
        File file = new File(FILE_PATH);
        if (file.exists()) {
            boolean delStatus = file.delete();
            System.out.println("File deleted? " + delStatus);
            // file.setReadable(true);
            // file.setWritable(true);
        } else {
            System.out.println("File doesn't exist");
        }
        try {
            file.createNewFile();

            prw = new PrintWriter(file); // <- this is line 136 from the exception
            prw.println(textData);
            prw.flush();
            result = 1;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (prw != null) {
                prw.close();
                System.out.println("Print writer closed.");
            }
        }
        return result;
    }

}

It doesn't seem to be an issue with the code, but perhaps something with my system?

Any clues on where I should be digging will be greatly appreciated.

  • Which OS are you on? This code is rife with bad code style issues. Using replaceAll where replace (which also replaces all - difference is regex) suffices, deplorable exception handling, lots of lines that do nothing (all those file.exists() calls), not using ARM blocks, and more - it's possible all that chaff is causing a few issues; it certainly makes it very hard to read through it all. – rzwitserloot Dec 09 '21 at 15:31
  • There is no reason to call `createNewFile` first - that is not what it is for (it's for doing some primitive synchronization between 2 different processes). `new PrintWriter(file)` will make the file if neccessary. – rzwitserloot Dec 09 '21 at 15:33
  • @rzwitserloot Windows 10. The code used to look different, these are the scars of attempting to root out the cause of my exception. And like I said the code runs fine in Eclipse, it's when I move to my exported jar is when it no longer behaves the same way. – Blorg Chacha Dec 09 '21 at 15:38
  • @rzwitserloot Thank you, I'll amend that once my sanity returns. I've been stuck with this issue since October. – Blorg Chacha Dec 09 '21 at 15:41
  • Print `file.getAbsoluteFile()` to verify where your code is trying to write to and test your writes with a new path FILE_PATH2 to rule out issues with un-closed readers. It is safer to write to new file and rename afterwards to avoid trashing the master copy. – DuncG Dec 09 '21 at 16:16
  • @DuncG Thank you for the suggestions. Absolute paths are identical. I thought about writing to a new file, but I cannot delete the old one to replace it, with the same "access is denied" exception. At least I get that with file.delete(). – Blorg Chacha Dec 09 '21 at 16:27
  • @K J I'm 100% sure code exits correctly, but even if it didn't I've been restarting my PC and reinstalled my OS and couldn't make the file editable to the jar executable. There just can't be anything using the file at all. – Blorg Chacha Dec 09 '21 at 16:38
  • 1
    Using `Files.delete(file.toPath())` may tell your more about the delete issue, and check Task Manager to see whether there are lingering versions of same application. – DuncG Dec 09 '21 at 16:42
  • @DuncG no lingering versions of application, but this method gave me another exception to research "java.nio.file.AccessDeniedException". – Blorg Chacha Dec 09 '21 at 17:16

1 Answers1

1

OK. I've gotten it to work finally. I had to turn off my Avast Antivirus temporarily and I was able to edit existing files. Specifically "Ransomware Protection" was protecting my Documents folder.

Thank you, commenters, for the help, couldn't have reached this conclusion without you! An answer in this question mentioned Comodo antivirus causing this same issue. I will create a new working directory in an unprotected folder, because Avast doesn't allow me to add a non-exe file as an exception.