Java platform long needed tools to work with file systems that are not so limited as those of prior releases to Java 7. Programmers require consistent behavior throughout many different platforms and efficiency in gathering file attributes and other data (or metadata). When it comes to platform specific capabilities of certain file systems, Java should benefit from them and provide the means to harness their power. Last but not least, programmer should always receive concrete description of exceptional situations during execution of their code.
File System API Overview
Before I get down to classes and interfaces of NIO.2 I find it beneficial to take a look at the big picture here. There are three key packages in NIO.2 when it comes to file system operations:
- Defines interfaces and classes for JVM that provide access to the files, file attributes and file systems.
- Defines interfaces and classes that provide access to file attributes and file system attributes.
- Defines service-provider classes for package
- Defines service-provider classes for package
Working with file systems always begins with class
java.nio.file.FileSystems. This class allows you to get instance of required file system by calling service-providing class
java.nio.file.spi.FileSystemProvider. Based on provided file system identifier
FileSystemProvider searches for suitable candidate and creates an instance afterwards. This instance is ready to be worked with. Each instance of any file system provides a method to retrieve instance of
java.nio.file.FileStore, which represents concrete physical storage for given file system. Each and every folder or file is represented by instances of classes implementing core NIO.2 interface,
java.nio.file.Path, which allows location of files.
Whole situation is described in following simplified class diagram. This gives reader better grasp of high-level structures used in NIO.2 as well as the feel of new library environment.
When designing new NIO library, creators had in mind high cohesion and expansion of its capabilities as well as the unification of the way file systems are being managed and worked with. They met these goals by completely rethinking the object model of the former
java.io package, which in turn, left developers with both legacy IO package and new alternative package. However this new package far surpasses the old one in both design and provided functionality.
If we looked back and explored the way things were done in
java.io we would conclude that classes and methods from this package are primarily oriented on work regarding instances (of implementing classes) of following interfaces:
Writer. There was basically only one class aggregating the whole operating apparatus for direct file system operations – class
java.io.File. There are some other classes in this package that work with
java.io.File instances. However the whole file system interaction is governed by
New API is conceptually build up as a set of interfaces for entities that cover in their abstraction all basic elements of file systems accompanied by a set of operative classes that cover basic file system operations. You can notice that classes in NIO.2 library are intentionally named differently from
java.io classes to avoid confusion.
The shift to this design approach was mainly influenced by the worldwide success in adoption and general popularity of collections library from
java.util package. This library has won number of rewards and works on the similar principles.
Before going through any code I would like to spend a little more time in the world of theory and focus on description of aforementioned classes and their capabilities.
Classes in detail
As indicated in the first chapter, the first class I am going to describe in more detail is
java.nio.file.spi.FileSystemProvider. This class is part of SPI interface of NIO.2 library and its primary concern is to provide access to objects representing file systems. This is an abstract class and any class meant to be providing actual file system access must extend this class and define concrete implementation of all its abstract methods. Each and every provider is identified by URI scheme. There is always default provider identified by scheme
Default provider is usually platform dependent, however it can be configured manually by setting of system environment variable
java.nio.file.spi.DefaultFileSystemProvider to class of choice. File system is instantiated during class loading in case of default provider. File system provider uses URI scheme to instantiate file systems, that allow JVM to access and modify given file systems. Unlike default provider, all other providers must have no argument constructor while default provider accepts one argument of
FileSystemProvider class stores immutable list of all installed file system providers starting with default provider located at first index. This list is also publicly available by calling
installedProviders method. Based on this description one can see, that
FileSystemProvider is typical implementation of factory design pattern. Class
java.nio.file.Files is typical client of this class since its methods delegate work to concrete instances of file system providers.
java.nio.file.FileSystems is a final class and it provides factory methods used to instantiate new file systems. There are exactly three use cases for this library class:
- get an instance of default file system
- get an instance of file system identified by URI scheme
- create an instance of file system
Execution of any of this class’ methods initializes default file system provider. Calling of any variant of method
newFileSystem causes loading of installed file system providers by class
FileSystemProvider. To install a file system provider just place a jar file in your applications class path or in the extensions directory, while this jar contains configuration file called
java.nio.file.spi.FileSystemProvider in the resource directory
META-INF/services containing fully qualified names of one or more file system providers. It is also possible to specify a class loader used to create new file system.
One of the most important classes in NIO.2 library is class
java.nio.file.FileSystem. This abstract class implements two interfaces:
- Implementation that comes from former versions of Java and could not be removed in order to preserve backward compatibility and so it does not violate contract in code written for older versions of Java
- Implementation enabling the use of
try-with-resourcesconstruct thus allowing us to benefit from automatic resource management during file system operations
- Implementation enabling the use of
This class serves as an interface for file system access from your Java code. It also dictates what factory methods every extending class representing concrete file system must implement. Instance of a file system serves as a factory for a few types of objects, which will be described in the following chapters.
In order to work with file systems you have to know that there are two states of file system:
closed. File systems become open right after the creation and they can be closed by calling method
close. The only exceptions are file systems created by default file system provider because they can not be closed. Another important property of file systems is whether they are open for read-only access or for read-write access. Both file system instances and provider instances are safe for multithreaded environment.
An abstract class
java.nio.file.FileStore represents physical storage, device, partition, memory or specific representation of storage of a file system. Primary concern of
FileStore instances is to provide information about storage – capacity, free and allocated space, support for write access, name and other specific storage attributes depending on concrete solution. Each storage can provide one or more attribute views. These views are specified by classes implementing
java.nio.file.attribute.FileStoreAttributeView interface. These views provide either read-only or read-write access to file store attributes.
Undoubtedly, the most important functionality during work with file systems is the ability to locate required file or folder. This feature is realized by brand new NIO.2 interface
java.nio.Path. Classes implementing this interface represent platform-dependent path to a file. Path interface extends following three interfaces:
- This allows programmer to execute lexicographical comparison of two abstract paths. Comparison is usually provider-dependent and in case of default provider also platform dependent
- This allows programmer to iterate over locations on the path. Iteration begins in the location closest to the file system root location and ends in concrete file or folder location (one that path represents)
- This allows programmer to register watch service and watch the path location for changes (this functionality will be discussed in great detail later in this series)
Path is always hierarchical and consists of sequence of file and folder names separated by special character, called delimiter. Path may even include a root component identifying file system hierarchy. A path that consists of only one location name that is empty is called empty path. If we would try to access this location it would give same result as accessing default file system location.
One of the main goals of new library , as mentioned before, was to improve cohesion of the overall design. One way to accomplish this was to remove one of the most significant shortcomings of
File class embodied both file location related information as well as file system related operations. By separating these two completely different concerns on conceptual and design level, one abstract entity arose to represent location on given file system. This location may or may not exist since it is only an abstract representation of said location that will be used in program code.
Oracle also warns that this interface should be implemented only by advanced programmers with an experience with file system development since its subject to further development in the future.
File class compatibility is guaranteed only for default file system provider.
Path instances are safe to use in multithreaded environment.
java.nio.file.Files consists of only static methods used to work closely with file system providers. This class provides basic functions for working with files and folders. One of the reasons for creation of this class was mentioned in previous chapter – design improvements. Basically, we can conclude, that
Files class provides methods representing basic file system related operations and is superior to
java.io.File class not only in the range of things it does, but also in the way these things are done.
This class truly demonstrates strong modularity of new NIO.2 library. And since methods take
Path instances as their arguments, programmers are effectively separated from any platform specific operations and can focus solely on application logic, while low-level work is being delegated on Java.
Files class is quite robust since it aggregates all basic operations with files and folders on a file system, such as:
- creation of temporary files and folders
- creation, copying, moving and deleting of files and folders
- creation of links and symbolic links
- retrieval of file store
- reading and writing file attributes
- writing to files
- navigation and traversing of the file tree structure
Point mentioned last was one of the new features added to Java with release of NIO.2 that allows developer to add custom implementation executed during file system traversing. Hierarchical structure of folders and files allows us to apply knowledge obtained from graph theory to optimize traversal-related operations.
walkFileTree proves this by implementing tree traversal algorithm known as DFS (Depth-first search) for navigation and hierarchy traversal. Root of the tree is one of methods arguments. In case of exceptional state, method throws either
RuntimeException. Traversing ends when all nodes are visited or when programmer explicitly stops it. Detailed description will be published later in this series.
Another library class that contains only two variants of one method for getting path instances of concrete file systems. Path, an abstract file system location, can be instantiated in two ways:
get(String first, String... more)
- Builds a path from provided strings. If
varargcontains more values, they are all joined together using file system delimiter to separate hierarchies. Usually its file system provider’s job to provide way of constructing
Pathinstances as well as to define file system delimiter. However, the most important thing to note is, that this method only creates paths for default file system.
- Builds a path from provided strings. If
- When it comes to working with more types of file systems this is the method to use, when creating path instances. Method uses concrete file system provider to create paths based on provided URI. URI scheme is compared with schemes of installed providers regardless of case sensitivity.
UserPrincipal and UserPrincipalService
File access rights are inseparable part of complex world of file systems. To control access to file, read and write privileges as well as ownership control NIO.2 introduces two new classes –
UserPrincipalLookupService from package
UserPrincipal instances represent users and groups. They are used to define access rights to file system objects. One of the most common ways to determine access level is to check user’s identity. We can find many examples of this concept, but one of the most common is file system implementing concept of ACL (Access Control List). This concept is based on list of rights that is used to determine, whether given user, group or process can access given file.
There are two ways to create
UserPrincipalLookupServiceto find particular
UserPrincipalby name (typically name of user account or group)
- Get specialized file attributes view and determine for example file owner as an instance of
Existing 3rd party implementations
It took a while but we have a first 3rd party implementation of FileSystem API by Google. Jimfs is an in-memory file system for Java 7 and above, implementing the java.nio.file abstract file system APIs. For more information and code please check out their GitHub project.
These were the most important classes and interfaces developers encounter while working with file systems in NIO.2. Of course there are many other classes but for now we can focus our attention on aforementioned ones. This short introductory article should provide you enough information to grasp the complexity of file system implementation and the work with them. In the next article I will demonstrate all this functionality on code samples.