1

I'm trying to take data from an ArrayList of a class and write it into a text file. It creates the temporary file, but doesn't do anything with it. It prints what I'm trying to put in the file and doesn't delete the temporary file. What am I doing wrong?

try
{
    File temp = new File("temp.txt");
    File file = new File(prop.getProperty("path"));
    BufferedWriter writer = new BufferedWriter(new FileWriter(temp));
    ArrayList<Contact> contacts = gestor.checkData();
                        
    if(temp.createNewFile())
    {
        for(int i = 0; i < contacts.size(); i++)
        {
            writer.write(contacts.get(i).getName());
            writer.write(contacts.get(i).getLastNames());
                                
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            String date = df.format(contacts.get(i).getBirthday());
                                
            writer.write(date);
            writer.write(contacts.get(i).getEmail());
        }
    writer.close();
    file.delete();
    temp.renameTo(file);
    }
}
catch (IOException e)
{
    e.printStackTrace();
}

The code of checkData() just returns the ArrayList<Contact> contactList.

Kirby
  • 15,127
  • 10
  • 89
  • 104
Eme
  • 25
  • 6
  • Where does it " prints what I'm trying to put in the txt"? I dont see any print statements. – matt Sep 30 '20 at 11:47
  • Me neither but when i execute it with eclipse it prints the contents of the ArrayList – Eme Sep 30 '20 at 11:51
  • 2
    `file` and `temp` are never actually used for anything, what was your intent with those two variables? – Joakim Danielson Sep 30 '20 at 11:53
  • @JoakimDanielson there is a rename/delete operation that happens at the end. Eme maybe you should print while you write. Also, can you try it without the rename/delete part and see if the temp file gets written ok? – matt Sep 30 '20 at 11:58
  • 1
    You have a syntax error on the line that starts with `String date...`. 'contactos2' instead of 'contacts'. – instanceof Sep 30 '20 at 12:00
  • @matt I see that, thank you, but where are they actually used? There is no real logic connected with them. – Joakim Danielson Sep 30 '20 at 12:00
  • @JoakimDanielson `temp` is the file where i want to write and `file` is kind of my data base so basically in `temp` i write whats in `file` + new things, then i delete `file` and change the name of `temp` to the name of `file` so its like ive updated `file` – Eme Sep 30 '20 at 12:01
  • @instanceof yes, sorry, the code is written is spanish so i changed the names of the variables so it'd be easier to read but i clearly forgot that one. I just edited it thanks – Eme Sep 30 '20 at 12:02
  • 1
    But then you need to actually use them, like when creating the BufferedWriter you should do `new FileWriter(temp)` – Joakim Danielson Sep 30 '20 at 12:02
  • @JoakimDanielson I just tried that and same thing happens, I edited the post to that too – Eme Sep 30 '20 at 12:08
  • @matt I just tried it without delete and rename and temp still shows up empty – Eme Sep 30 '20 at 12:08
  • `renameTo` returns a boolean -- true if it succeeds and false otherwise. It would be wise to check that. – Roddy of the Frozen Peas Sep 30 '20 at 12:10
  • 1
    Have you tried placing a breakpoint and debugging? – Roddy of the Frozen Peas Sep 30 '20 at 12:12

1 Answers1

3

Many problems with your code:

resource leakage

Something like a new FileWriter is a resource. Resources MUST be closed. As a consequence, you should never make resources (you usually know it when you make a resource: you either call new X where X is clearly representative of a resource, or you call something like socket.getInputStream or Files.newBufferedReader... unless you do it 'right'. There are only two ways to do it right:

try (FileWriter w = new FileWriter(...)) {
   // use it here
}

or, if you need the resource to be a field of your class, then the only safe way is to turn your class into a resource: make it implements AutoClosable, make a close method, and now the burden of using that try syntax is on whomever uses your class. There is no way to safely do this stuff without try-with-resources*.

Charset messup

files are bytes. not characters. In addition, the filesystem has no clue what the 'encoding' is. How do you turn , or for that matter, é into bytes? The answer depends on charset encoding. The problem with all methods that turn bytes into characters or vice versa that are NOT in the java.nio.file package is that they use 'platform default encoding'. That's a funny way of saying 'the worst idea ever', as that will 'work' on your machine and will pass all tests, and then fail in production, at the worst possible moment. The solution is to never rely on platform default, ever. If you really really intend to use that, make it explicit. Unfortunately, FileWriter, until java11, has no way of specifying charset encoding, which makes it an utterly useless class you cannot actually ever use without writing buggy code. So don't. I suggest you switch to the new file API, which defaults to UTF-8 (quite a sane default), and can do a lot of complicated things in one-liners.

No newlines

You just .write all this data. write does exactly what it says, and writes precisely the string. It does not print anything more. Specifically, it does not print any newlines. Surely you did not intend to just write, say, JoeSteel1990-10-01 to the file, all in one jumbled mess like that? write \n to avoid this, or preconstruct the entire string just as you want it (with newlines), and then write that.

on unexpected condition silently do nothing

Your code will silently do nothing if the 'temp' file already exists. It sounds like the design is that this is a weird situation (as the temp file is 'renamed' right after). As a general rule of thumb, 'silently do nothing' is entirely the wrong instinct to have when you run into scenarios you know are unlikely or seemingly impossible. The right instinct is the exact opposite: Fail as spectacularly as you can. (The best option, of course, is to think about what the weird scenario means and handle it properly, but that's not always possible). So, instead of that, try:

if (!temp.creatNewFile()) {
    throw new RuntimeException("That is weird - tempfile already exists: " + temp);
}

That's the right mindset: If weird things happen, blow up, as fast as you can, with enough detail so you know what's going wrong. (The most likely 'correct' way to handle this is to just delete the temp file. The whole point of that temp file is that if it's still there when you enter this code, that the previous attempt failed halfway through, so just delete the product of the failed operation, it isn't useful).

exception handling.

Whilst common in examples and even IDE templates, you're not doing it right. You should NEVER handle an exception by printing something and carrying on. Think about it: If something went wrong, continuing with the code execution is either going to cause nasty problems (as you surely did not consider that one of the steps in your program failed when you write your code), or is going to cause another error. And if all errors are handled like this, one thing goes wrong and you get 85 error traces in your logs. That's not useful. If a problem occurs and you do not know how to handle it, do NOT continue running. The only sensible 'I do not know how to handle this' exception handling is:

catch (IOException e) {
    throw new RuntimeException("unhandled", e);
}

sometimes a better exception than RuntimeException is possible (such as UncheckedIOException or ServletException, it depends on the situation). Also, sometimes the right answer is to just throws the exception onwards. Remember, public static void main can (and usually should!) be declared as throws Exception.

Putting it all together

try {
    Path temp = Paths.get("temp.txt");
    Path file = Paths.get(prop.getProperty("path"));
    ArrayList<Contact> contacts = gestor.checkData();

    try (BufferedWriter writer = Files.newBufferedWriter(temp)) {
      for (Contact contact : contacts) {                        
          writer.write(contact.getName());
          writer.write("\n");
          writer.write(contact.getLastNames());
          writer.write("\n");
          DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
          String date = df.format(contact.getBirthday());
          writer.write(date);
          writer.write("\n");
          writer.write(contact.getEmail());
          writer.write("\n");
      }
    }
    Files.delete(file);
    Files.move(temp, file);
} catch (IOException e) {
    throw new UncheckedIOException(e);
}

The above will probably fail, but it will fail telling you exactly why it failed. For example, it might tell you that the directory you are trying to write to does not exist, or you have no write access. Whereas your code will then silently just do nothing.

*) For you java pros out there, sure, you can handroll your own try/finally loops. Let me know how many programmers new to java ever managed to dodge every mine in the minefield when you do that. Until you are fairly wintered, this rule of thumb becomes effectively a rule of law: You can't do it safely without t-w-r. Let's keep it simple.

matt
  • 10,892
  • 3
  • 22
  • 34
rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • use `BufferedWriter.newLine()` instead of `.write("\n")` to handle the different end of line characters on different systems – Kirby May 26 '22 at 18:49