Search code examples
javafile-permissionsexecutable-jar

Unable to modify or delete text file created by my .jar executable after closing and relaunching the executable


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.


Solution

  • 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.