0

My application writes to Excel files. Sometimes the file can be used, in that case the FileNotFoundException thrown and then I do not know how to handle it better.

I am telling the user that the file is used and after that message I do not want to close the application, but to stop and wait while the file is available (assuming that it is opened by the same user). But I do not understand how to implement it. file.canWrite() doesn't work, it returns true even when the file is opened, to use FileLock and check that the lock is available I need to open a stream, but it throws FileNotFoundException (I've been thinking about checking the lock in a busy wait, I know that it is not a good solution, but I can't find another one).

This is a part of my code if it can help somehow to understand my problem:

File file = new File(filename);
FileOutputStream out = null; 
try {
    out = new FileOutputStream(file);
    FileChannel channel = out.getChannel();
    FileLock lock = channel.lock();
    if (lock == null) {
        new Message("lock not available");
            // to stop the program here and wait when the file is available, then resume 
    }
    // write here
    lock.release();
}
catch (IOException e) {
    new Message("Blocked");
    // or to stop here and then create another stream when the file is available
}

What makes it more difficult for me is that it writes to different files, and if the first file is available, but the second is not, then it will update one file and then stop, and if I restart the program, it will update it again, so I can't allow the program to write into files until all of them are available.

I believe that there should be a common solution, since it must be a common issue in Windows to deal with such cases, but I can't find it.

Olga
  • 309
  • 4
  • 16

1 Answers1

1

To wait until a file exists you can make a simple loop:

File file = new File(filename);
while (!file.exists()) {
    try { 
        Thread.sleep(100);
    } catch (InterruptedException ie) { /* safe to ignore */ }
}

A better solution could be using WatchService but it's more code to implement.

The File.canWrite method only tells you if a path can be written to; if the path names a file that doesn't exist it will return false. You could use the canRead method instead of exists in a loop like above.

To use a file locks, the file has to exist first, so that wouldn't work either.


The only way to be sure you can write to a file is to try to open it. If the file doesn't exist, the java.io API will create it. To open a file for writing without creating you can use the java.nio.file.Files class:

try (OutputStream out = Files.newOutputStream(file.toPath(),
                                              StandardOpenOption.WRITE))
{
    // exists and is writable
} catch (IOException) {
    // doesn't exist or can't be opened for writing 
}
Joni
  • 108,737
  • 14
  • 143
  • 193
  • I think there's a timing issue here (although unlikely or not applicable for OP) - the file can be deleted after the `file.exists()` check but before the `lock`. – Bernhard Barker Aug 21 '13 at 15:58
  • file.exists() returns true when the file is opened. – Olga Aug 21 '13 at 16:12
  • Is that a problem? The question is hard to understand. You are getting a `FileNotFoundException` for a file that exists? – Joni Aug 21 '13 at 16:21
  • Ah now I get it @Olga: on Windows the JVM throws a `FileNotFoundException` when any error happens when opening the file. If the file is already open in another application, opening in another may fail with `ERROR_SHARING_VIOLATION` (depending on how the first app opened it). It seems there's no way to detect if the file opened besides retrying and catching the exception, even with Windows API you have to loop around the `CreateFile` API call. See http://support.microsoft.com/kb/316609 – Joni Aug 21 '13 at 16:36
  • And if I don't want to create a new file, but just to check if they are available for writing? When I open FileOutputStreams and then close them (I do it to check if all files are available before writing) and then open again for writing, I get InvalidFormatException: Package should contain a content type part [M1.13] – Olga Aug 21 '13 at 17:25
  • Opening and closing a `FileOutputStream` creates an empty file. If you try to open an empty file with Apache POI you'll probably get an `InvalidFormatException`. There is a way to open a file for writing that throws an exception if it doesn't exist, instead of creating it, see the update. Does that solve something for you? – Joni Aug 21 '13 at 18:37
  • Yes, thank you Joni. Unfortunately, I noticed your update too late, I have found another way to check - to use RandomAccessFile. RandomAccessFile raf = new RandomAccessFile(file, "rw"); I am not sure if it's a correct way, but I tested and looks like it works. I just close it immediately if it doesn't throw an exception. – Olga Aug 22 '13 at 13:56
  • Opening a `RandomAccessFile` will also create a new file if it doesn't already exist, just like `FileOutputStream`. If creating empty files is not a problem that's OK, but from your previous comment I understood that you don't want to create a new file. – Joni Aug 22 '13 at 14:02
  • First I check file.exists(). If false, then I create (I have a method which creates new files with headers, so I don't want the system to create empty files itself). If true, then I open RandomAccess and check if they are available. After opening with RandomAccess I do not get InvalidFileFormat exception, which I had with FileOutputStream. – Olga Aug 22 '13 at 14:50
  • Ah OK, then the problem was that FileOutputStream *truncated* the existing file. Opening a RandomAccessFile in read-write mode doesn't do that, so case closed. – Joni Aug 22 '13 at 14:59