Java 7 - Path Manipulation Using java.nio.file.Path

Lately I had the feeling it's about time to starts looking into Java 7 EA, it has been under development for some time now and by the latest news it should be released soon. So I downloaded JDK 7-EA and Netbeans 7 (Eclipse doesn't support Java 7 language changes yet) and looked for interesting changes in the release notes, the first few I've noticed are the I/O enhancements on which I decided to write a post or two starting with a short coverage of the new APIs for path manipulation in the new java.nio.file.Path interface.

java.nio.file.Path

On first glance this new interface seems like java.io.File on steroids (easy path manipulations and directory watching) however this is not planned to be a full replacement of it, the new interface is missing some of the java.io.File APIs (like the ones accessing the file system – like mkdir()). However since it has an easy API to convert from and to java.io.File we can use both classes and enjoy a wider and improved API.

Instantiation a Path Object
Paths manipulation tasks include many pitfalls to take care of: different operating systems have different files separators and path formats, a given path can be absolute or relative, two paths can reference the same file system or different ones. Many of these challenges are being abstracted away by this new interface making the daily development task easier - but before we start we have to instantiate a Path object. Since the java.nio.file.Path type is an interface we need a way to obtain a concrete instance from one of its factories: java.nio.file.FileSystem or java.io.File.


Using java.nio.file.FileSystem
FileSystem and FileSystems are also two new types in Java 7’s java.nio.file package. Each java.nio.file.FileSystem concrete implementation is specialized to handle a different underlying file system type (some examples: FAT12/16/32/..., ext2/3/4, network drives, compressed archives, secured disk and so on). The java.nio.file.FileSystems final class is the factory from which we can obtain FileSystem instances:

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

// Obtaining a path from the default FileSystem and list its name and concrete type
Path path = FileSystems.getDefault().getPath("myPath", "subPathA", "subPathB");
System.out.printf("Created path (%s) of type %s%n", path, path.getClass().getName());

//The output was: 
// Created path (myPath\subPathA\subPathB) of type sun.nio.fs.WindowsPath

The code above creates a Path instance pointing to 'myPath/subPathA/subPathB' relatively to the JVM's current working directory (the default file system is the one on which the current working directory exists). It is worth noticing that a FileSystem includes a collection of FileStores - for example in Windows each disk is a FileStore and not a FileSystem.

Using java.io.File
Another way to instantiate a Path object is using the new toPath() method in java.io.File. This method is more than just an easy way to construct Path instances; when it is combined with the toFile() method in the Path interface it is a useful and easy mechanism to combine both classes APIs and easily integrate legacy and new code as illustrated below:


// Legacy code invoking a new method which gets and returns Path instances:
// Path newMethodUsingPath(Path in) {....}
File legacyFile = new File("c:/eyal/desktop");
....
legacyFile = newMethodUsingPath(legacyFile.toPath()).toFile();
....

Update (24-Jun-2011):
As commented by Konrad Twardowski below there is a third way to create Path objects – using the java.nio.file.Paths.get(URI) and java.nio.file.Paths.get(String, String…) static methods. For example:
Path path = java.nio.file.Paths.get("myPath", "subPathA", "subPathB");

Path Manipulation
A big part of the Path API is dedicated to path manipulation, conceptually a Path instance is an object that may be used to locate a file (including directories) in the filesystem; it is a hierarchical structure of directories and a file name elements separated by a separator (as with java.io.File we can safely use the forward slash regardless of the underlying O/S). A Path instance may have (when using an absolute path instance) a special root component identifying the filesystem. The element which is the farthest from the path's first element represents the name of the file or directory to which this path refers; all other name elements are directory names. An empty path is constructed of one name element which is empty and it refers to the default directory of the filesystem (the current working directory). Almost, if not all, of path manipulation methods described in this section do not access the filesystem – these are logical manipulations done in memory regardless of the actual availability of the path elements in the file system.


Path Elements Iteration
The Path interface extends the Iterable<Path> interface so it can be iterated either using an explicit iterator or with a foreach loop returning a Path object for each iteration. The iteration order is from the element which is closest to the root of the directory hierarchy (excluding the root itself!) toward the element which is farthest. The sample below iterates over Path elements and prints a tree structured of it.

// Iterate over the path - the next example prints out a tree like structure 
// of a path
      
//I'm using the java.io.File to obtain a Path instance
path = new File("c:/tmp/p1/p2/file.txt").toPath(); 
int t=1;
for (Path subPath : path) {
  System.out.format("%"+t+"s%s%n", ' ', subPath);
  t+=2;
}

// The output is
 tmp
   p1
     p2
       file.txt

Looking at the output it is important to notice that the actual root ("c:\") was skipped by the iterator even though I was using an absolute path.
The getParent() Method
The getParent() method returns a path's parent which is defined in the javadoc as: "The parent of this path object consists of this path's root component, if any, and each element in the path except for the farthest from the root in the directory hierarchy". The following code sample is, almost, the reverse iteration of the previous iteration example using the getParent() method. Unlike the iteration sample the getParent() methods returns the path's root (in my case “c:\”) as well.

// Sample of path.getParent()
      
//I'm using the java.io.File to obtain a Path instance
Path p2 = new File("c:/tmp/p1/p2/file.txt").toPath(); ;
while (p2!=null) {
   System.out.println(p2);
   p2=p2.getParent();
}

// The output is
c:\tmp\p1\p2\file.txt
c:\tmp\p1\p2
c:\tmp\p1
c:\tmp
c:\

Notice the path's root: c:\ which is returned by this method.

Normalize
The normalize() method returns a new Path instance from which all redundant name elements in the original path have been removed – the outcome is implementation dependent but as a general rule it calculates the 'simplest path' available considering any element which is either '.' (current directory) or '..' (parent directory). This method can be very useful in many cases, for example think of a user input containing elements such as '.' or '..' - the normalize method can be used to simplify this path. The following code example finds the normalized path of 'c:/tmp/../tmp1/././././../tmp3/a.txt':


// Sample of path.normalize()
      
Path p2 = new File("c:/tmp/../tmp1/././././../tmp3/a.txt").toPath();
System.out.format("%s is normalized to %s%n", p2, p2.normalize());

// The output is
c:\tmp\..\tmp1\.\.\.\.\..\tmp3\a.txt is normalized to c:\tmp3\a.txt

Again this method, as all others in that section, does not access the filesystem - it only parses the path.

Absolute, Relative and Path Resolution
The next two methods for path manipulations are toAbsolutePath() and relativize(). The first one is straight forward: it returns a Path instance which is the absolute representation of a given Path. The resolution into an absolute path is filesystem dependent but it typically resolves relative paths against the file system default directory (for absolute paths there is no need for resolution). We can check to see if a path is already an absolute one by using the isAbsolute() method - if a given path is already absolute the toAbsolutePath() simply returns the path instance.

The relativize(Path p) method is an interesting one - unlike all of the path manipulation methods we have seen so far this method is the first one which involves two Path instances. The method returns a new Path instance constructed of the relative path from this object to the other Path (the argument). The javadoc explains the behavior of this method in a very formal language but maybe the easiest way to describe it is to say that the method returns a relative path from this to the argument.  Here are two examples:

// Sample of path.relativize()
Path p2 = new File("tmp1/tmp2/tmp3").toPath();
Path p3 = new File("tmp1/tmp2/tmp4").toPath();
System.out.format("%s relativized with %s: %s%n", p2, p3, p2.relativize(p3));

// The output of the above is: 
// tmp1\tmp2\tmp3 relativized with tmp1\tmp2\tmp4: ..\tmp4
        


p2 = new File("e:/tmp1/tmp2/tmp3").toPath();
p3 = new File("d:/tmp1/tmp2/tmp4").toPath();
System.out.format("%s relativized into %s: %s%n", p2, p3, p2.relativize(p3));

// This one throws an exception since on windows we cannot build a relative path from disk E:\ to disk D:\

The first sample in the section above illustrates a successful invocation of the relativize() method - and indeed the relative path between 'tmp1/tmp2/tmp3' and 'tmp1/tmp2/tmp4'  is '../tmp4' (I'm using the forward slash but from Java perspective it is interchangeable with the backward slash). The second one throws an exception (IllegalArgumentException with the "'other' has different root" message) since on windows I cannot build a relative path that will take me from one folder into another folder in a different disk.


And there are more...

The java.nio.file.Path class provides many other methods for easy path manipulation. Since their are generally simpler than the ones above I didn't cover them. Some examples are:
  • resolve(Path other), resolveSibling(Path other)
  • getFileName() - similar to Linux's basename command
  • getNameCount() and getName(int index)
  • subpath(int beginIndex, int endIndex)
Conclusions
The new Path class has some very interesting APIs which can make developers path manipulation tasks much easier and efficient than in earlier versions. java.nio.file.Path is just one of many improvements introduced in the new JDK release (and even this class itself has more new features than the ones described above) - more about that in future posts.




Update: A follow up post explains how to use Java 7 API for file-system directories is available here.

Comments

Anonymous said…
mkdir is not missing, look at Files.createDirectories.
Hi and thanks for this article.

There is a shorter way to obtain a Path:
Path path = java.nio.file.Paths.get("myPath", "subPathA", "subPathB");
Very nice stuff.

Thanks for sharing!
Vijay Bhore said…
I am new to java.nio world.

I have a log file app.log inside my Java module. (I am using Intellij 11).

In a module, i have test folder, i have a local file app.jar.

I want to read this file into InputStream using Files.newInputStream(path) method.

The problem is that in windows, i have to give complete value for input path as,

Path path = Paths.get("C:\\Perforce\\depot\\Project\\module\\src\\test\\a\\b\\c\\app.jar");

I am not sure if somebody gets this code and has similar folder structure on their machine to access app.jar

I have kept the file app.jar in local folder where my test is written. Is there a way through which i can generalize this Path? if my Test class is under the same folder (My TestClass path is: "C:\\Perforce\\depot\\Project\\module\\src\\test\\a\\b\\c\\TestClass.java"), is there any mechanism to avoid mentioning the complete local path?

Thanks, Vijay Bhore
Gabor said…
Hello everybody (special, Vijay),

I want to access an embedded path of my NetBeans project. Something like "myProject=>Source Packages=>data" which I normally address in NetbBeans simply as "/data". But the jio access allways the normal Win filesystem, not the embedded path in my NB project. How can I address this "inner" path pls.?
Robert said…
Very nice explanation of Path class. It helped me a lot.

Popular posts from this blog

New in Spring MVC 3.1: CSRF Protection using RequestDataValueProcessor

Hibernate Exception - Simultaneously Fetch Multiple Bags

Hibernate Derived Properties - Performance and Portability