Friday, November 9, 2012

Java 7: File Filtering using NIO.2 - Part 3

Hello all. This is Part 3 of the File Filtering using NIO.2 series. For those of you who haven't read Part 1 or Part 2, here's a recap.

NIO.2 is a new API for I/O operations included in the JDK since Java 7. With this new API, you can perform the same operations performed with java.io plus a lot of great functionalities such as: Accessing file metadata and watching for directory changes, among others. Obviously, the java.io package is not going to disappear because of backward compatibility, but we are encouraged to start using NIO.2 for our new I/O requirements. In this post, we are going to see how easy it is to filter the contents of a directory using this API. There are 3 ways in order to do so, we already reviewed two similar ways in Part 1 and Part 2, but now we are going to see a more powerful approach.

What you need
NetBeans 7+ or any other IDE that supports Java 7

Filtering content of a directory is a common task in some applications and NIO.2 makes it really easy. The classes and Interfaces we are going to use are described next:
  • java.nio.file.Path: Interface whose objects may represent files or directories in a file system. It's like the java.io.File but in NIO.2. Whatever I/O operation you want to perform, you need an instance of this interface.
  • java.nio.file.DirectoryStream: Interface whose objects iterate over the content of a directory.
  • java.nio.file.DirectoryStream.filter<T>: A nested interface whose objects decide whether an element in a directory should be filtered or not.
  • java.nio.file.Files: Class with static methods that operates on files, directories, etc.

The way we are going to filter the contents of a directory is by using objects that implement the java.nio.file.DirectoryStream.filter<T> interface. This interface declares only one method +accept(T):boolean which as the JavaDoc says: "returns true if the directory entry should be accepted". So it's up to you to implement this method and decide whether a directory entry should be accepted based on whatever attribute you want to use: by hidden, by size, by owner, by creation date, etc. This is important to remember, using this method you are no longer tied to filter only by name, you can use any other attribute.

If you only want directories, you can use the java.nio.file.Files class and its +isDirectory(Path, LinkOption...):boolean method when creating the filter:

//in a class...

    /**
     * Creates a filter for directories only
     * @return Object which implements DirectoryStream.Filter
     * interface and that accepts directories only.
     */
    public static DirectoryStream.Filter<Path> 
                                 getDirectoriesFilter() {

        DirectoryStream.Filter<Path> filter = 
                           new DirectoryStream.Filter<Path>() {

            @Override
            public boolean accept(Path entry) throws IOException 
            {
                return Files.isDirectory(entry);
            }
        };

        return filter;
    }


Or if you only want hidden files, you can use the java.nio.file.Files class and its +isHidden(Path):boolean method when creating the filter:

//in a class...

    /**
     * Creates a filter for hidden files only
     * @return Object which implements DirectoryStream.Filter
     * interface and that accepts hidden files only.
     */
    public static DirectoryStream.Filter<Path> 
                                 getHiddenFilesFilter() {

        DirectoryStream.Filter<Path> filter = 
                           new DirectoryStream.Filter<Path>() {

            @Override
            public boolean accept(Path entry) throws IOException 
            {
                return Files.isHidden(entry);
            }
        };

        return filter;
    }


Or if you want files belonging to a specific user, you have to ask for a user and compare it with the owner of the directory entry. To obtain the owner of a directory entry, you can use the java.nio.file.Files class and its +getOwner(Path, LinkOption...):UserPrincipal method (watch out, not all OS support this).
To obtain a specific user on the filesystem use the java.nio.file.FileSystem class and its +getUserPrincipalLookupService():

//in a class...

    /**
     * Creates a filter for owners
     * @return Object which implements DirectoryStream.Filter
     * interface and that accepts files that belongs to the 
     * owner passed as parameter.
     */
    public static DirectoryStream.Filter<Path> 
          getOwnersFilter(String ownerName) throws IOException{

        UserPrincipalLookupService lookup = FileSystems.getDefault().getUserPrincipalLookupService();

        final UserPrincipal me = 
                    lookup.lookupPrincipalByName(ownerName);

        DirectoryStream.Filter<Path> filter = 
                           new DirectoryStream.Filter<Path>() {

            @Override
            public boolean accept(Path entry) throws IOException 
            {
                return Files.getOwner(entry).equals(me);
            }
        };

        return filter;
    }


The following piece of code defines a method which scans a directory using any of the previous filters:

//in a class...
    
    /**
     * Scans the directory using the filter passed as parameter.
     * @param folder directory to scan
     * @param filter Object which decides whether a 
     * directory entry should be accepted
     */
    private static void scan(String folder
                           , DirectoryStream.Filter<Path> filter) 
    {
        //obtains the Images directory in the app directory
        Path dir = Paths.get(folder);
        //the Files class offers methods for validation
        if (!Files.exists(dir) || !Files.isDirectory(dir)) {
            System.out.println("No such directory!");
            return;
        }
        //validate the filter
        if (filter == null) {
            System.out.println("Please provide a filter.");
            return;
        }

        //Try with resources... so nice!
        try (DirectoryStream<Path> ds = 
                      Files.newDirectoryStream(dir, filter)) {
            //iterate over the filtered content of the directory 
            int count = 0;
            for (Path path : ds) {
                System.out.println(path.getFileName());
                count++;
            }
            System.out.println();
            System.out.printf(
                     "%d entries were accepted\n", count);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

We can execute the previous code passing the following parameters to the main method (check the source code at the end of this post):

  • Directory to scan: C:\ or / dependening on your OS. 
  • Filter: hidden

When executing the code we get the following:




In a windows machine, you can obtain the hidden files using the command: dir /AAH Notice that we are getting the same result:




And on my Linux virtual machine:




Using the command ls -ald .* we get similar results:



Again, Write once, run everywhere!

I hope you enjoyed the File Filtering using NIO.2 series. One last word, all the filtering methods we reviewed worked on one directory only, if you want to scan a complete tree of directories, you'll have to make use of the java.nio.file.SimpleFileVisitor class.


Click here to download the source code of this post.


See ya!

References:

Reese Richard and Reese Jennifer (2012). Java 7 New Features Cookbook. United Kingdom: Packt Publishing Ltd.