javatry-with-resourcesdirectorystream

How does "try-with-resources" actually work in the context of this program that deletes all files and folders below a given directory node


This program deletes all files and folders beneath a given node.

*EDIT*

I have the following "test" directory structure on drive K:

Folder K:\garbage\
f_00000b (file)
f_00000f ( " )
f_0000b3 ( " )
dir1    [FOLDER]

Folder K:\garbage\dir1\
abc.pdf (file)
b12.pdf (file)
b36.pdf (file)
dir2   [FOLDER]

Folder K:\garbage\dir1\dir2\
A.pdf   (file)
a1.pdf  (file)
A2.pdf  (file)

*END EDIT*

Program works because I stumbled onto "try-with-resources", the line surrounded with all the ///////////////////////// being "the resource".

import java.io.IOError;
import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import javax.swing.JOptionPane;

public class CreatingDirectories {

    public static void main(String[] args) {

      deleteEverythingBelowThisNode(Paths.get("K:/garbage/dir1/dir2"));
    }

    static void deleteAllFilesIn(String ps){

      Path p = Paths.get(ps);

      try ////////////////////////////////////////////////////////////////
         (DirectoryStream<Path> paths = Files.newDirectoryStream(p)) /////
      { //////////////////////////////////////////////////////////////////

        for(Path q: paths)
          Files.delete(q);

      }catch(NotDirectoryException e){
        System.out.println("Not directory: " + p.toString() + "--" + e);
      }catch(DirectoryNotEmptyException e){
        System.out.println("Not empty: " + p.toString() + "--" + e);
      }
      catch(IOException e){
        System.out.println("IO: " + p.toString() + "--" + e);
      }
      catch(IOError e){
        System.out.println("IO ERROR: " + e);
      }
    }

    static void deleteEverythingBelowThisNode(Path p){

      String            sep        = p.getFileSystem().getSeparator();
      ArrayList<String> pathPieces = new ArrayList<>() ;
      String []         paths      = new String[p.getNameCount()];

      for(int i = 0 ; i < p.getNameCount() ; i++){
        pathPieces.add(p.getName(i).toString());
        paths[i] = p.getRoot().toString();
      }

      for(int i = 0; i < p.getNameCount() ; i++)
        for(int k = 0; k <= i; k++)
          paths[i] += pathPieces.get(k) + sep;

      for(int k = p.getNameCount() - 1; k >= 0; k--)
        deleteAllFilesIn(paths[k]);
    }
}

I understand that "try-with-resources" is necessary: program works with it, not without it.

But I don't understand why and I don't know how it solved the original problem, which I now describe.

I originally had situated the "resources" above the try-block, like this, which seems perfectly natural:

      DirectoryStream<Path> paths = Files.newDirectoryStream(p);
      try {
           for...

With the program structure otherwise identical except for moving that one line as shown above, all files and subfolder had been successfully deleted from a folder, but DirectoryNotEmptyException was thrown. Windows Explorer confirmed that the directory was empty after the program terminated because of the exception.

Why was the exception thrown on an empty directory?

From the horse's mouth, "The try-with-resources statement ... declares ... an object that ... is closed at the end of the statement."

Closing happens at the end of the statement, so at the end of the loop. How did the exception not occur even with try-with-resources?

As it now is, after looping through the entire node, everything beneath it has been deleted.

So what did try-with-resources actually do to enable deleting an empty folder that could not be deleted without try-with-resources?

These don't seem like stupid questions or a trivial situation.

Did a DirectoryNotEmptyException actually occur anyway, but try-with-resources somehow handled it? I can't believe I'm asking that, since it does seem like a stupid question, but what actually happened to make the program run as expected?


Solution

  • On Windows, you cannot delete a file or directory that is still open. (On Unix, on the other hand, this is not a problem - the file will be deleted from the directory structure when you delete it and from disk when you close it. But that's on Unix.)

    So if you don't use the try-with-resources statement to close the directory stream, you will still have subdirectories open at the moment that you try to delete the files in the parent directory, and that attempt to the subdirectory that is still open will fail. Since you ignore exceptions (you just print them), the subsequent attempt to delete the parent directory will also fail with a DirectoryNotEmptyException since you didn't delete all the subdirectories.

    You can verify if this is really the case. When you do not use try-with-resources, make sure that you explicitly close the directory stream after you delete all files in the directory (using paths.close();)

    That should have the same effect as the try-with-resources block (unless an exception occurs - to guarantee exactly the same behavior as try-with-resources, you need to put paths.close(); in a finally block).