Java I/O and NIO.2

NIO.2 cookbook, Part 2

Part 2 of a 3-part series on NIO.2 presents path-related recipes, file/directory-testing recipes, and attribute-oriented recipes

Java I/O and NIO.2

Show More
1 2 3 Page 2
Page 2 of 3

Listing 4. MorePathOp.java

import java.nio.file.Path;
import java.nio.file.Paths;

public class MorePathOp
{
   public static void main(String[] args)
   {
      Path path1 = Paths.get("reports", ".", "2015", "jan");
      System.out.println(path1);
      System.out.println(path1.normalize());
      path1 = Paths.get("reports", "2015", "..", "jan");
      System.out.println(path1.normalize());
      System.out.println();

      path1 = Paths.get("reports", "2015", "jan");
      System.out.println(path1);
      System.out.println(path1.relativize(Paths.get("reports", "2016", "mar")));
      try
      {
         System.out.println(path1.relativize(Paths.get("/reports", "2016", 
                                                       "mar")));
      }
      catch (IllegalArgumentException iae)
      {
         iae.printStackTrace();
      }
      System.out.println();

      path1 = Paths.get("reports", "2015");
      System.out.println(path1);
      System.out.println(path1.resolve("apr"));
      System.out.println(path1.resolve("/apr"));
   }
}

Listing 4's main() method first demonstrates the normalize() example involving the current directory. It then demonstrates this example involving the parent directory (..). The result is reports/jan.

Next, main() demonstrates the previous relativize() example and then demonstrates that you cannot relativize a relative path against an absolute path. If you attempt to do so, IllegalArgumentException is thrown.

Finally, main() demonstrates the previous resolve() example and then shows what happens when you resolve against an absolute path. Instead of reports/2015/apr, you obtain /apr.

You can perform additional operations on a Path object, such as converting a Path to an absolute Path via Path toAbsolutePath(), and determining if two Paths are the same via the boolean equals(Object other) method.

Testing file accessibility

Q: How do I determine if a file is executable, readable, or writable?

A: The Files class provides the following static methods for determining if a file is executable, readable, or writable:

  • boolean isExecutable(Path path): Check that the file represented by path exists and that the virtual machine has appropriate privileges to execute it. Return true when the file exists and is executable; return false when the file doesn't exist, execute access would be denied because the virtual machine has insufficient privileges, or access cannot be determined.
  • boolean isReadable(Path path): Check that the file represented by path exists and that the virtual machine has appropriate privileges to open it for reading. Return true when the file exists and is readable; return false when the file doesn't exist, read access would be denied because the virtual machine has insufficient privileges, or access cannot be determined.
  • boolean isWritable(Path path): Check that the file represented by path exists and that the virtual machine has appropriate privileges to open it for writing. Return true when the file exists and is writable; return false when the file doesn't exist, write access would be denied because the virtual machine has insufficient privileges, or access cannot be determined.

You would typically call one of these methods before performing the appropriate execute, read, or write file operation. For example, you might check a file to learn if it's writable before attempting to write to the file, as follows:

if (!Files.isWritable(Paths.get("file")))
{
   System.out.println("file is not writable");
   return;
}
byte[] data = { /* some comma-separated list of byte data items */ };
Files.write(Paths.get("file"), data);

However, there's a problem with this code. After checking to see if file is writable, and before writing to the file, an external process might access the file and make it read-only. The resulting file-writing code would then fail.

The race condition between time of check and time of use is commonly known as Time of check to time of use (TOCTTOU -- pronounced TOCK-too), and is why the Javadoc says that the results of these methods are immediately outdated.

TOCTTOU is probably not much of a problem for many applications where there is extremely low probability of external interference. However, it's a significant problem for security-sensitive applications where a hacker might use it to steal user authentication information. For these applications, you might want to avoid isWritable() and its companions, and place your file I/O code in a try/catch construct instead.

Testing file or directory existence

Q: How do I determine if a file or directory exists or doesn't exist?

A: The Files class provides the following static methods for determining if a file or directory exists or doesn't exist:

  • boolean exists(Path path, LinkOption... options): Test the file/directory represented by path to determine if it exists. By default, symbolic links are followed. However, if you pass LinkOption.NOFOLLOW_LINKS to options, symbolic links are not followed. Return true when the file/directory exists; return false when the file/directory doesn't exist or its existence cannot be determined.
  • boolean notExists(Path path, LinkOption... options): Test the file/directory represented by path to determine if it doesn't exist. By default, symbolic links are followed. However, if you pass LinkOption.NOFOLLOW_LINKS to options, symbolic links are not followed. Return true when the file/directory doesn't exist; return false when the file/directory exists or its existence cannot be determined.

You would probably call exists() before attempting an operation that throws an exception when the file doesn't exist. For example, you might test for existence before deleting a file:

Path path = Paths.get("file");
if (Files.exists(path))
   Files.delete(path);

Note that !exists(path) isn't equivalent to notExists(path) (probably because !exists() isn't atomic, whereas notExists() is atomic). Also, if exists() and notExists() return false, the existence of the file cannot be verified. Finally, as with the accessibility methods, the results of these methods are immediately outdated and should be avoided or at least used very carefully in security-sensitive applications.

More testing operations

Q: What other kinds of testing operations can I perform on a Path object?

A: The Files class provides static methods for determining if a path is a directory, if a path is hidden, if a path is a regular file, if two paths identify the same file, and if a path is a symbolic link:

  • boolean isDirectory(Path path, LinkOption... options) -- specify LinkOption.NOFOLLOW_LINKS when you don't want this method to follow symbolic links.
  • boolean isHidden(Path path)
  • boolean isRegularFile(Path path, LinkOption... options) -- specify LinkOption.NOFOLLOW_LINKS when you don't want this method to follow symbolic links.
  • boolean isSameFile(Path path, Path path2)
  • boolean isSymbolicLink(Path path)

Consider isHidden(Path), which returns true when the file identified by the Path argument is considered to be hidden. Listing 5's application uses this method to filter the names of a directory's hidden files, which are then output.

Listing 5. ListHiddenFiles.java

import java.io.IOException;

import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ListHiddenFiles
{
   public static void main(String[] args) throws IOException
   {
      if (args.length != 1)
      {
         System.err.println("usage: java ListHiddenFiles directory");
         return;
      }

      Path dir = Paths.get(args[0]);
      DirectoryStream<Path> stream = Files.newDirectoryStream(dir); 
      for (Path file: stream) 
         if (Files.isHidden(file))
            System.out.println(file);
   }
}

main() first validates the command line, whose solitary argument identifies the directory to search for hidden files. This String object is then converted into a Path object.

The Path object is passed to Files's DirectoryStream<Path> newDirectoryStream(Path dir) method, which returns a java.nio.file.DirectoryStream object for iterating over all entries in the directory identified by the path.

DirectoryStream implements Iterable, enabling this object be used with the enhanced for statement to loop over directory entries. For each entry, isHidden() is called; only those entries where this method returns true are output.

Compile Listing 5 (javac ListHiddenFiles.java) and run the application; for example, java ListHiddenFiles C:\. On my Windows platform, I observed the following output:

C:\hiberfil.sys
C:\pagefile.sys

Getting and setting single attributes

Q: How do I get or set a Path object's attributes?

A: Files provides several static methods for getting or setting a Path object's attributes:

  • Object getAttribute(Path path, String attribute, LinkOption... options)
  • FileTime getLastModifiedTime(Path path, LinkOption... options)
  • UserPrincipal getOwner(Path path, LinkOption... options)
  • Set<PosixFilePermission> getPosixFilePermissions(Path path, LinkOption... options)
  • boolean isDirectory(Path path, LinkOption... options)
  • boolean isHidden(Path path)
  • boolean isRegularFile(Path path, LinkOption... options)
  • boolean isSymbolicLink(Path path)
  • Path setAttribute(Path path, String attribute, Object value, LinkOption... options)
  • Path setLastModifiedTime(Path path, FileTime time)
  • Path setOwner(Path path, UserPrincipal owner)
  • Path setPosixFilePermissions(Path path, Set<PosixFilePermission> perms)
  • long size(Path path)

Previously, I presented isDirectory(), isHidden(), isRegularFile(), and isSymbolicLink(), which classify a Path object. These methods also return a Path object's directory, hidden, regular file, and symbolic link attribute values.

Listing 6 presents a small application that obtains a Path object's last-modified time, owner, and size via the getLastModifiedTime(Path), getOwner(Path), and size(Path) methods; and outputs these values.

Listing 6. PathAttrib.java (version 1)

import java.io.IOException;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathAttrib
{
   public static void main(String[] args) throws IOException
   {
      if (args.length != 1)
      {
         System.err.println("usage: java PathAttrib path-object");
         return;
      }

      Path path = Paths.get(args[0]);
      System.out.printf("Last modified time: %s%n", Files.getLastModifiedTime(path));
      System.out.printf("Owner: %s%n", Files.getOwner(path));
      System.out.printf("Size: %d%n", Files.size(path));
   }
}

Compile Listing 6 (javac PathAttrib.java) and run the application. For example, I observe the following output from java PathAttrib PathAttrib.java:

Last modified time: 2015-03-18T17:56:15.802635Z
Owner: Owner-PC\Owner (User)
Size: 604

Q: What is an attribute view and how do I work with it?

A: The Javadoc for various attribute methods (e.g., getAttribute(), getPosixFilePermissions(), setAttribute(), and setPosixFilePermissions()) refers to interface types in the java.nio.file.attribute package. These interfaces identify different attribute views, which are groups of related file attributes. An attribute view maps to a file system implementation (e.g., POSIX) or to a common functionality (e.g., file ownership).

NIO.2 provides AttributeView as the root of its attribute view interface hierarchy. This interface declares a getName() method for returning an attribute view's name. AttributeView is extended by FileAttributeView, which "is a read-only or updatable view of non-opaque values associated with a file in a file system." FileAttributeView offers no methods, but serves as a superinterface for more specific file-oriented attribute views:

  • BasicFileAttributeView: a view of basic attributes that must be supported by all file system implementations
  • DosFileAttributeView: an extension of the basic attribute view with the legacy DOS operating system read-only, hidden, system file, and archived flags
  • PosixFileAttributeView: an extension of the basic attribute view with attributes supported on file systems that support the POSIX family of standards (e.g., UNIX). These attributes include file owner, group owner, and the nine related access permissions.
  • FileOwnerAttributeView: supported by any file system implementation that supports the concept of a file owner
  • AclFileAttributeView: supports reading or updating a file's Access Control Lists (ACL)
  • UserDefinedFileAttributeView: enables support of user-defined metadata

Note that a file system implementation might support only the basic file attribute view, or it might support several of the aforementioned file attribute views (and even support other attribute views not included in the previous list).

Files provides the <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) generic method for returning a specific type of file attribute view; for example, AclFileAttributeView view = Files.getFileAttributeView(path, AclFileAttributeView.class);. This method returns null when the attribute view isn't available.

You typically don't deal with FileAttributeView or its subinterfaces. Instead, you would work with the getAttribute() and setAttribute() methods. Each method's attribute parameter requires a string adhering to the following syntax:

[view-name:]attribute-name

view-name identifies a FileAttributeView that identifies a set of file attributes. If specified, it is one of basic, dos, posix, owner, or acl. When not specified, the view name defaults to basic, which is the attribute view that identifies the basic set of file attributes common to many file systems. (There is no name for user-defined file attribute views.) attribute-name is the attribute's name. The various attribute view interfaces identify specific names.

Each method throws java.lang.UnsupportedOperationException when the attribute view isn't supported. To find out what attribute views are supported, invoke FileSystem's Set<String> supportedFileAttributeViews() method.

Listing 7 presents an application that outputs the default file system's supported attribute views, and outputs the values of all attributes in the (always supported) basic and (when supported) acl and dos attribute views for the specified path.

1 2 3 Page 2
Page 2 of 3