Paths in NIO.2

In order to work with file system one must first be able to point to files and directories. The first thing that needs to be understood is the role of java.nio.file.Path class, the way instances are created and its functionality. As mentioned in previous articles, Path is just an abstraction of the file system location. This allows for the situations when directory does not even have to exist. NIO.2 presents more elegant solutions for getting the object representing file system location. This shields programmer from platform specific problems.

In general, Path instances allow two types of operations:

syntactic operations

any operations related to the Path representation itself – hierarchy traversal, conversion, comparison and so on

file operations

operations that modify location, state or contents of a file represented by a path instance

Absolute paths

First, the most common type of path that we can create is an absolute path. You may know it by the names complete path or final path. Path contains complete directory hierarchy down to the final leaf node. To define this type of path, developer is free to decide whether to specify it by a single string or by entering every hierarchical level as a separate string. The only difference between them is in the absence of path delimiter in the definition of the path.

Java

1

2

3

4

// each of the following calls gets given path from the default file system

Path path1=Paths.get("c:/src/java/nio/file/Path.java");

Path path2=Paths.get("c:","src/java/nio","file","Path.java");

Path path3=Paths.get("c:","src","java","nio","file","Path.java");

The code snippet above is pretty straightforward – it creates 3 instances of Path. All these paths are equivalent since they point to the same location on the default file system. This allows us to compare these paths with the result of equivalency. This approach to getting path instances brings one big improvement – programmer does not have to specify file system delimiter since it is provided by Java. This would not be possible to do with good old java.io.File (unless you would be willing to nest several calls of File(String parent, String child) to follow hierarchical structure).

Java

1

2

3

4

// each of the following calls gets given path from the default file system

With regard to the last paragraph please note, that when retrieving path instances on for example Linux file systems, first / symbol is required. In this example it is actually the name of root file system location – not to be confused with file path delimiter.

Relative paths

Relative (or incomplete) path is a type of path that is only a subset of an absolute path. You can easily identify relative path declaration simply by looking at the syntax of the declaration itself. Relative path is always relative to something concrete:

path starting with delimiter

Paths declared this way are always relative to the root directory of the file system

path starting without delimiter

Paths declared this way are always relative to the current directory

Lets look at the following example where we create two relative paths for each test platform and using conversion method toAbsolutePath obtain absolute versions of these paths.

Java

1

2

3

4

5

6

7

8

9

10

11

// executed on Windows

Path pathRelativeToCurrentDirectoryWin=Paths.get("src/main/java");

Path pathRelativeToRootFSLocationWin=Paths.get("/src/main/java");

System.out.println("Windows:\nPath relative to current directory: "+pathRelativeToCurrentDirectoryWin.toAbsolutePath());

Paths of non-default file systems

One might ask a question what about file systems other than default file system. To answer this we first need to take a look at how file system instances are created. Based on the nature of path, it is only logical that they are involved in the file system creation process. Lets take a ZIP file system for example.

Returning to the question in the beginning of this paragraph, well, there is an easy solution to retrieve path instances for files and directories in other than default file systems as well. This means that all information provided above are applicable to any given file system, as long as Java has access to required file system implementation. NIO.2 brings a transparent way of handling paths, whether they are representing default file system location or some other file system location.

Lets take a look at some basic ways of getting paths from ZIP file system. First, and let’s be honest, the most simple way is to create a path instance, use it to create file system instance and retrieve paths from this new file system. Whole situation is described in following snippet:

These two ways are pretty nice, but imagine the situation when we want to access a path within target file system directly. NIO.2 presents simple solution for this type of situations. Lets say, we want to work with a path, that points to the Path interface source code stored in src.zip file in my Java installation folder. For this type of paths, programmer has to specify also path within target zip file. So, step one is to formulate URI, that incorporates both paths and is correctly prefixed. In order to make such URI, the URI is composed as follows: jar:file:<path to zip file>!<path to file stored in zip file>. Following code displays the situation nicely.

What has happened? Well, if I take a look back here, chapter FileSystem clearly states, that file system can be in two states – open and closed. File systems are implicitly opened right after the creation and you can access them to retrieve desired path only when they are instantiated and in open state. Since this condition was not fulfilled the first time, lets modify previous example so it works in a desired way.

* Please note that ZIP file system has only one root location represented by / – ZIP file system will be described in one of the future articles

It’s now, that we finally get desired output from previous example:

1

/java/nio/file/Path.java

Path conversion

It is necessary for any practical application to be able to convert path instances to other object representations. Whether we want to work with pre Java 7 code, or allow other developers to use our APIs, we need to be able to integrate NIO.2 constructs with the outside world. Path conversion works with all basic types of paths right out of the box. But I have not mentioned one particular type of path yet.

This special path is so-called real path. I have not mentioned it yet, because it is quite similar to the absolute path with only one distinction, that directly affects path conversion. In general, real path is derived from an absolute path. Real path consists of hierarchy elements names that are case-sensitive, while locating of files on the file system is not case-sensitive. Its definition is both implementation and platform dependent.

Path instances can be converted to six basic representations:

Absolute path

You can convert a path to its absolute form using toAbsolutePath method. By doing so the resulting path contains whole hierarchical structure and may contain shortcut notation.

Normalized path

In order to remove shortcut notation from an absolute path you can convert it to its normalized form using normalize method.

File

To ensure backwards compatibility with pre Java 7 releases programmers are armed with toFile method allowing tighter integration between NIO.2 and other libraries.

Real path

When you apply method toRealPath to an absolute path you will end up with real path.

String

Most common conversion method to create string representation of paths – method toString.

URI

Using method toUri creates URI representation of given path providing detailed view of file location (and in case of ZIP file system also the view of ZIP-containing file system)

Designers of NIO.2 library were aware of the need to integrate this library with an existing code. In order to preserve code compatibility with programs working with java.io.File, designers introduced toPath method, so the conversion from File to Path is also possible.

However, there is also another interesting way to create and convert path instances in one simple step – path combination. Combining paths is a technique allowing programmer to use already defined path and append it with partial path. This way proves to be very efficient during the declaration of paths with fixed parent hierarchy. Paths created this way are absolute paths. Combining is controlled by two methods:

resolve

Method appends provided partial path to the end of base path. If the partial path does not contain root file system location, both paths are simply concatenated. In case of root file system location being present in partial path we have a problem. In this case the whole operation is strongly implementation dependent and hence is not specified.

resolveSibling

Based on parent element, method retrieves parent location and concatenates it with partial path from method attribute. If fixed path does not have a parent element (empty path) or partial path turns out to be an absolute path, then partial path is returned.

To better understand path combination please take a look at following code snippet:

Path comparison

The last of notable path properties is the ability to compare them. This is pretty clear from the declaration of Path interface, since it implements Comparable<T> interface. In general, there are three basic ways of comparing paths each with its own unique processing and area of application:

equals

Method fulfilling equals contract from java.lang.Object

compareTo

Method performing lexicographical comparison of given paths. Ordering based on this method is highly implementation dependent on file system provider and in case of default file system provider even platform dependent. It is important to bear in mind that this is purely lexicographical comparison, hence no file system access is required (nor performed) and files located by provided paths do not need to exist.

Files.isSameFile

Method that verifies whether two files from provided paths are identical or not. Even though, this method does not compare paths, I decided to include it here, since it provides very useful functionality within this topic. Method works in two steps:

Step: Compare two provided paths using equals method – if true is returned than comparison ends with true return value. If paths are from two different file systems than method returns false. Otherwise step 2 is executed.

Step: Actual comparison of files located by compared paths. Behavior of this step is highly implementation dependent and might require file system access.