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

My previous post launched a three-part series on NIO.2 recipes. I presented three recipes for copying files, deleting files and directories, and moving files. In this post, I present path-related recipes (such as obtaining paths and retrieving path information), file/directory-testing recipes (such as testing file/directory existence), and attribute-oriented recipes.

Obtaining paths

Q: How do I obtain a java.nio.file.Path object?

A: You obtain a Path object, which locates a file or directory in a file system via a sequence of name elements, by calling either of the following static methods in the java.nio.file.Paths class:

  • Path get(String first, String... more): Convert a path string, or a sequence of strings that when joined form a path string, to a Path object. This method throws java.nio.file.InvalidPathException when the path string cannot be converted to a Path because the path string contains invalid characters, or the path string is invalid for other file system-specific reasons.

    The Path object is obtained by invoking the default java.nio.file.FileSystem instance's Path getPath(String first, String... more) method.

  • Path get(URI uri): Convert the path uniform resource identifier (URI) argument to a Path object. This method throws java.lang.IllegalArgumentException when preconditions on uri don't hold (e.g., uri must include a scheme component); java.nio.file.FileSystemNotFoundException when the uri-identified file system doesn't exist and cannot be created, or the provider identified by the URI's scheme component isn't installed; and java.lang.SecurityException when any installed security manager denies an unspecified permission to access the file system.

    The Path object is obtained by iterating over the installed java.nio.file.spi.FileSystemProvider instances for a provider that supports the URI's scheme, and then invoking the provider's Path getPath(URI uri) method.

The first get() method lets you compose a path out of an arbitrary number of components; for example, Path path = Paths.get("a", "b");. This method places an appropriate file system-specific separator character between these components when you convert it to a string. For example, System.out.println(path); results in a\b being output on a Windows platform.

You can hard-code a separator character between components, as in Path path = Paths.get("a", "\\", "b");. However, this isn't a good idea. On a Windows platform, you would end up with a\b. However, on a platform where the colon (:) is used as a separator, you would most likely observe InvalidPathException, pointing out that the backslash character is illegal.

You might decide to use File.separator in place of a hard-coded separator character. However, this isn't a good idea either. NIO.2 supports multiple file systems, but File.separator is associated with the default file system. Attempting to obtain a path containing File.separator in the context of a file system that doesn't recognize this constant's separator character would probably result in InvalidPathException.

Instead of hard-coding a separator character or specifying File.separator, use FileSystem's String getSeparator() method. getSeparator() returns the separator character for the file system associated with the invoking FileSystem instance. For example, FileSystem.getDefault().getSeparator() returns the default file system's separator character.

I briefly demonstrated the first get() method in Part 1 of this series. Listing 1 presents the source code to another small application that reinforces what I've said about separator characters.

Listing 1. ObtainPath.java (version 1)

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

public class ObtainPath
{
   public static void main(String[] args)
   {
      Path path = Paths.get("a", "b");
      System.out.println(path);

      path = Paths.get(FileSystems.getDefault().getSeparator() + "a", "b", "c");
      System.out.println(path);

      path = Paths.get("a", ":", "b");
      System.out.println(path);
   }
}

The second example shows that the separator character is also used to signify the root directory, but there is a better way to get this directory, which I'll show later in this post.

Compile Listing 1 (javac ObtainPath.java) and run the application (java ObtainPath). On a Windows platform, I observe the following output:

a\b
\a\b\c
Exception in thread "main" java.nio.file.InvalidPathException: Illegal char <:> at index 2: a\:\b
	at sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182)
	at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153)
	at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77)
	at sun.nio.fs.WindowsPath.parse(WindowsPath.java:94)
	at sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:255)
	at java.nio.file.Paths.get(Paths.java:84)
	at ObtainPath.main(ObtainPath.java:15)

By the way, what happens when you pass null as one of get()'s path components; for example, path = Paths.get("a", null, "b");?

The second get() method lets you convert a URI to a path. Listing 2 presents a small application that demonstrates a couple of the problems to watch out for when using this method.

Listing 2. ObtainPath.java (version 2)

import java.net.URI;
import java.net.URISyntaxException;

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

public class ObtainPath
{
   public static void main(String[] args) throws URISyntaxException
   {
      try
      {
         Path path = Paths.get(new URI(""));
         System.out.println(path);
      }
      catch (IllegalArgumentException iae)
      {
         System.err.println("You cannot pass a URI object initialized to the " +
                            "empty string to get().");
         iae.printStackTrace();
      }

      try
      {
         Path path = Paths.get(new URI("nntp://x"));
         System.out.println(path);
      }
      catch (FileSystemNotFoundException fsnfe)
      {
         System.err.println("No file system associated with the specified scheme.");
         fsnfe.printStackTrace();
      }
   }
}

Compile Listing 2 and run the application. You should observe the following exceptional output:

You cannot pass a URI object initialized to the empty string to get().
java.lang.IllegalArgumentException: Missing scheme
	at java.nio.file.Paths.get(Paths.java:134)
	at ObtainPath.main(ObtainPath.java:15)
No file system associated with the specified scheme.
java.nio.file.FileSystemNotFoundException: Provider "nntp" not installed
	at java.nio.file.Paths.get(Paths.java:147)
	at ObtainPath.main(ObtainPath.java:27)

The first example demonstrates IllegalArgumentException from a missing URI scheme component. The second example demonstrates FileSystemNotFoundException because no provider exists for the nntp scheme.

Retrieving path information

Q: What kinds of information can I retrieve from a Path object?

A: The Path interface declares several methods for retrieving the individual name elements and other kinds of information from a Path object. The following list describes these methods:

  • Path getFileName(): Return the name of the file or directory (the farthest element from the root in the directory hierarchy) denoted by this path as a Path object, or return null when the path has no elements.
  • Path getName(int index): Return the name element located at index, which must range from 0 (the closest element to the root) to one less than getNameCount()'s value. This method throws IllegalArgumentException when index is negative, or greater than or equal to the number of elements in the path -- or the path has zero elements.
  • int getNameCount(): Return the number of elements in the path. Zero is returned when the path represents a root directory only.
  • Path getParent(): Return the parent path or null when this path doesn't have a parent. The parent path consists of this path's root component (if any) and each element in the path except for the element that's farthest from the root in the directory hierarchy.
  • Path getRoot(): Return the path's root component as a Path object, or return null when this path doesn't have a root component.
  • boolean isAbsolute(): Return true when the path is absolute (complete in that it doesn't need to be combined with other path information to locate a file); otherwise, return false.
  • Path subpath(int beginIndex, int endIndex): Return a relative Path that's a subsequence of the name elements of this path. The name elements range from beginIndex to one less than endIndex. IllegalArgumentException is thrown when beginIndex is negative or greater than or equal to the number of elements; or when endIndex is less than or equal to beginIndex or larger than the number of elements.
  • String toString(): Return the string representation of this path. The separator character that's used to separate name elements is obtained by invoking the current FileSystem object's getSeparator() method.

Listing 3 presents the source code to a small application that demonstrates these path information-retrieval methods. It also shows the proper way to add a root directory to a path.

Listing 3. RetrievePathInfo.java

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

public class RetrievePathInfo
{
   public static void main(String[] args)
   {
      dumpPathInfo(Paths.get("a", "b", "c"));

      FileSystem fs = FileSystems.getDefault();
      Iterable<Path> roots = fs.getRootDirectories();
      for(Path root: roots)
      {
         dumpPathInfo(Paths.get(root.toString(), "a", "b", "c"));
         break;
      }
   }

   static void dumpPathInfo(Path path)
   {
      System.out.printf("Path: %s%n", path);
      System.out.printf("Filename: %s%n", path.getFileName());
      System.out.println("Components");
      for (int i = 0; i < path.getNameCount(); i++)
         System.out.printf("   %s%n", path.getName(i));
      System.out.printf("Parent: %s%n", path.getParent());
      System.out.printf("Root: %s%n", path.getRoot());
      System.out.printf("Absolute: %b%n", path.isAbsolute());
      System.out.printf("Subpath [0, 2): %s%n%n", path.subpath(0, 2));   
   }
}

Listing 3: RetrievePathInfo.java

Listing 3 creates two paths -- a relative path and an absolute path -- and invokes the previously listed methods to return various kinds of information about them. For the second path, FileSystem's Iterable<Path> getRootDirectories() method is called to retrieve the default file system's root directories. The first root directory is prepended to the path prior to dumping path information.

Compile Listing 3 (javac RetrievePathInfo.java) and run the application (java RetrievePathInfo). On a Windows platform, I observe the following output:

Path: a\b\c
Filename: c
Components
   a
   b
   c
Parent: a\b
Root: null
Absolute: false
Subpath [0, 2): a\b

Path: C:\a\b\c
Filename: c
Components
   a
   b
   c
Parent: C:\a\b
Root: C:\
Absolute: true
Subpath [0, 2): a\b

Notice that isAbsolute() returns true for the path with the prepended root directory. If you prepended only the file system's separator, as in Paths.get(FileSystems.getDefault().getSeparator() + "a", "b", "c"), isAbsolute() would return false for this path on a Windows platform -- an absolute path on a Windows platform must also include a drive specifier; for example, C:.

More path operations

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

A: The Path interface provides methods for removing path redundancies, creating a relative path between two paths, joining two paths, and more. The first three operations are performed by the following methods:

  • Path normalize()
  • Path relativize(Path other)
  • Path resolve(Path other) (and the Path resolve(String other) variant)

normalize() is useful for removing redundancies from a path. For example, reports/./2015/jan includes the redundant . (current directory) element. When normalized, this path becomes the shorter reports/2015/jan.

relativize() creates a relative path between two paths. For example, given current directory jan in the reports/2015/jan hierarchy, the relative path to navigate to reports/2016/mar is ../../2016/mar.

resolve() is the inverse of relativize(). It lets you join a partial path (a path without a root element) to another path. For example, resolving apr against reports/2015 results in reports/2015/apr.

Listing 4 demonstrates these methods in the context of these and other examples. (For convenience, I hard-coded / in two places, but should have obtained a root directory instead.)

1 2 3 Page 1
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.