Our first example demonstrates the newFileVisitorAPI.
Imagine a scenario in which you want to traverse a directory tree recursively, stopping at each file and directory under that tree and having your own callback methods invoked for each entry found. In previous Java versions, this would have been a painful process involving recursively listing directories, inspecting their entries, and invoking the callbacks yourself. In Java 7, this is all provided via theFileVisitorAPI, and using it couldn't be simpler.
The first step is to implement your ownFileVisitorclass. This class contains the callback methods that the file-visitor engine will invoke as it traverses the file system. TheFileVisitorinterface consists of five methods, listed here in the typical order they would be called during traversal (There stands for eitherjava.nio.file.Pathor a superclass):
FileVisitResult preVisitDirectory(Tdir)is called before the entries in that directory are visited. It returns one of theFileVisitResult's enum values to tell the file visitor API what to do next.
FileVisitResult preVisitDirectoryFailed(Tdir, IOException exception)is called when a directory could not be visited for some reason. The exception that caused the visit to fail is specified in the second parameter.
FileVisitResult visitFile(Tfile, BasicFileAttributes attribs)is called when a file in the current directory is being visited. The attributes for this file are passed into the second parameter. (You'll learn more about file attributes in this article'sFile attributessection.)
FileVisitResult visitFileFailed(Tfile, IOException exception)is called when the visit to a file has failed. The second parameter specifies the exception that caused the visit to fail.
FileVisitResult postVisitDirectory(Tdir, IOException exception)is called after the visit to a directory and all its subdirectories has completed. The exception parameter is null when the directory visit has been successful, or it contains the exception that caused the directory visit to end prematurely.
To help developers save time, NIO.2 has kindly provided an implementation of theFileVisitorinterface:java.nio.file.SimpleFileVisitor. This class is as basic as it gets: for the *Failed()methods, it just rethrows the exception, and for the other methods it continues without doing anything at all! What's useful about this class is that you can use anonymous classes to override only the methods you want; the rest of the methods are implemented by default.
Listing 1 shows how we create an exampleFileVisitorinstance:
Listing 1.FileVisitorimplementation
FileVisitor<Path> myFileVisitor = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir) {
System.out.println("I'm about to visit the "+dir+" directory");
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) {
System.out.println("I'm visiting file "+file+" which has size " +attribs.size());
return FileVisitResult.CONTINUE;
}
};
TheFileVisitorimplementation inListing 1should print a message for each directory and file it visits and also give the size of the files from theirBasicFileAttributes.
Next we want to create aPathfrom which to start our file visiting. This is done using thejava.nio.Pathsclass:
Path headDir = Paths.get("headDir");
We can use either of two methods on thejava.nio.Filesclass to start the tree traversal:
public static void walkFileTree(Path head, FileVisitor<? super Path> fileVisitor)walks the file tree under the head directory, invoking the callback methods implemented infileVisitoras it goes.
public static void walkFileTree(Path head, Set<FileVisitOption> options, int depth, FileVisitor<? super Path> fileVisitor)is similar to the preceding method but gives you two additional parameters to specify visit options and how many directories deep into the file tree the traversal should go.
We'll use the simpler version of thewalkFileTree()method to start the process of walking the file tree:
Files.walkFileTree(headDir, myFileVisitor);
Suppose the directory structure looks like this:
headDir
|--- myFile1
|--- mySubDirectory1
| \myFile2
\--- mySubDirectory2
|--- myFile3
\--- mySubdirectory3
\---myFile4
Listing 2 shows the output from this example:
Listing 2.FileVisitoroutput
I'm about to visit the headDir directory
I'm about to visit the headDir\mySubDirectory2 directory
I'm about to visit the headDir\mySubDirectory2\mySubDirectory3 directory
I'm visiting file headDir\mySubDirectory2\mySubDirectory3\myFile4 which has size 2
I'm visiting file headDir\mySubDirectory2\myFile3 which has size 2
I'm about to visit the headDir\mySubDirectory1 directory
I'm visiting file headDir\mySubDirectory1\myFile2 which has size 2
I'm visiting file headDir\myFile1 which has size 2
As you can see, the file traversal is depth first but not necessarily in any alphabetical order within a directory. Our callback methods were invoked as expected, and we can see that all the files in the tree have been listed and all the directories have been visited.
In only about 15 lines, we've created a file visitor that will walk any file tree you give it and inspect the files contained therein. This example is basic, but the callbacks can be implemented to be as complex as you want.
Directory watching
This second example covers the exciting world of the newWatchServiceAPI and its associated classes.
The scenario for this example is simple: You'd like to track whether any files or directories in a particular directory (or directories) are being created, modified, or deleted. You might use this information to update a file listing in a GUI display or perhaps to detect the modification of configuration files that could then be reloaded. In previous Java versions, you must implement an agent running in a separate thread that keeps track of all the contents of the directories you wish to watch, constantly polling the file system to see if anything relevant has happened. In Java 7, theWatchServiceAPI provides the ability to watch directories. It removes all the complexity of writing your own file system poller and, where possible, is based upon existing native system APIs for better performance.
The first step is to create aWatchServiceinstance via thejava.nio.file.FileSystemsclass. We won't go into the details of file systems in this article, so just know that in most cases you'll want to get the default file system and then invoke itsnewWatchService()method:
WatchService watchService = FileSystems.getDefault().newWatchService();
Now that we have our watch service instance, we want to register a path to watch. We create aPathobject for the directory we wish to watch in a slightly different way from the file visitor example, so that we can use itsFileinstance again later:
File watchDirFile = new File("watchDir");
Path watchDirPath = watchDirFile.toPath();
ThePathclass implements thejava.nio.file.Watchableinterface, and that interface defines theregister()method we'll be using in this example.WatchKey register(WatchService watchService, WatchEvent.Kind<?>... events)registers thePaththis method is called on with the specifiedwatchServicefor the specific events given. Events trigger a notification only if they are specified in the register call.
For the defaultWatchServiceimplementation, thejava.nio.file.StandardWatchEventKindclass defines three static implementations ofwatchEvent.Kindthat can be used in theregister()calls:
StandardWatchEventKind.ENTRY_CREATEindicates that a file or directory has been created within the registeredPath. AnENTRY_CREATEevent is also triggered when a file is renamed or moved into this directory.
StandardWatchEventKind.ENTRY_MODIFYindicates that a file or directory in the registeredPathhas been modified. Exactly which events constitute a modification is somewhat platform-specific, but suffice it to say that actually modifying the contents of a file always triggers a modify event. On some platforms, changing attributes of files can also trigger this event.
StandardWatchEventKind.ENTRY_DELETEindicates that a file or directory has been deleted from the registeredPath. AnENTRY_DELETEevent is also triggered when a file is renamed or moved out of this directory.
For our example, let's watch theENTRY_CREATEandENTRY_MODIFYevents, but notENTRY_DELETE:
WatchKey watchKey = watchDirPath.register(watchService,
StandardWatchEventKind.ENTRY_CREATE, StandardWatchEventKind.ENTRY_MODIFY);
OurPathis now registered to be watched, and theWatchServicewill work away silently in the background, watching that directory intently. The sameWatchServiceinstance can watch multiple directories by using the samePathcreation andregister()calls we've shown above.
The observant among you may have spotted that theregister()method call returns a class we haven't come across before:WatchKey. This class represents your registration with theWatchService. Whether you hang onto this reference or not is up to you, because theWatchServicereturns the relevantWatchKeyto you when an event is triggered. However, do note that there are no method calls to find outwhichdirectory theWatchKeywas registered with, so if you're watching multiple directories you may wish to track whichWatchKeys go with whichPaths. When you're done with a particularWatchKeyand the events it's registered for, you can cancel its registration with theWatchServicesimply by calling itscancel()method.
Now that ourPathis registered, we can check in with theWatchServiceat our convenience to see if any of the events we were interested in has occurred.WatchServiceprovides three methods for checking if anything exciting has happened:
WatchKey poll()returns the nextWatchKeythat has had some of its events occur, ornullif no registered events have happened.
WatchKey poll(long timeout, TimeUnit unit)takes a timeout and time units (java.util.concurrent.TimeUnit). If an event occurs during the specified time period, this method exits, returning the relevantWatchKey. If there are noWatchKeysto return by the end of the timeout, this method returnsnull.
WatchKey take()is similar to the preceding methods, except it will wait indefinitely until aWatchKeyis available to return.
Once aWatchKeyhas been returned by one of these three methods, it will not be returned by a furtherpoll()ortake()call until itsreset()method is invoked, even if events it is registered for occur. Once aWatchKeyis returned by theWatchService, you can inspect its events that have been triggered by calling theWatchKey'spollEvents()method, which returns a List ofWatchEvents.
To illustrate, the simple example in Listing 3 continues from theWatchKeywe registered earlier:
Listing 3. UsingpollEvents()
// Create a file inside our watched directory
File tempFile = new File(watchDirFile, "tempFile");
tempFile.createNewFile();
// Now call take() and see if the event has been registered
WatchKey watchKey = watchService.take();
for (WatchEvent<?> event : watchKey.pollEvents()) {
System.out.println(
"An event was found after file creation of kind " + event.kind()
+ ". The event occurred on file " + event.context() + ".");
}
When executed, the code inListing 3prints:
An event was found after file creation of kind ENTRY_CREATE. The event occurred
on file tempFile.
An event was found after file creation of kind ENTRY_MODIFY. The event occurred
on file tempFile.
As you can see, we got theENTRY_CREATEevent for the newly createdtempFileas expected, but we also got another event. On some operating systems, a file creation or deletion can sometimes also produce anENTRY_MODIFYevent. If we had not registered to receive modification events, then we would have only gotten theENTRY_CREATEevent, whatever the OS.
Extended sample code (demonstrating file modification and deletion for the registeredWatchKeyin this section's example) is included in the sample codedownload.
File attributes
Our third and final example covers the new APIs for getting and setting file attributes using the classes under thejava.nio.file.attributepackage.
The new APIs offer access to more file attributes than you can imagine. In previous Java releases, you can get only a basic set of file attributes (size, modification time, whether the file is hidden, and whether it is a file or directory). To get or modify any further file attributes, you must implement this yourself in native code specific to the platforms you want to run on — not exactly easy. Brilliantly, Java 7 should allow you to read and, where possible, modify an extended set of attributes in a simple way via thejava.nio.file.attributeclasses, completely abstracting away the platform-specific nature of these operations.
Sevenattribute viewsare available in the new APIs, some of which are specific to the operating system. These "view" classes allow you to get and set whichever attributes they are associated with, and each one has a counterpart attribute class that contains the actual attribute information. Let's take a look at them in turn.
AclFileAttributeViewandAclEntry
AclFileAttributeViewallows you to get and set the ACL and file-owner attributes of a particular file. ItsgetAcl()method returns aListofAclEntryobjects, one for each permission set on the file. ItssetAcl(List<AclEntry>)method allows you to modify that access list. This attribute view is only available for Microsoft® Windows® systems.
BasicFileAttributeViewandBasicFileAttributes
This view class allows you to get a set of — no surprise — basic file attributes, building on those available in previous Java versions. ItsreadAttributes()method returns aBasicFileAttributesinstance containing details of last modified time, last access time, creation time, size, and type of file (regular file, directory, symbolic link, or other). This attribute view is available on all platforms.
Let's look at an example of this view. To get a file attribute view for a particular file we start, as always, by creating aPathobject for the file we're interested in:
File attribFile = new File("attribFile");
Path attribPath = attribFile.toPath();
To get the file attribute view we want, we'll use thegetFileAttributeView(Class viewClass)method onPath. To get theBasicFileAttributeViewforattribPath, we simply call:
BasicFileAttributeView basicView
= attribPath.getFileAttributeView(BasicFileAttributeView.class);
As described earlier, to get theBasicFileAttributesfromBasicFileAttributeView, we just call itsreadAttributes()method:
BasicFileAttributes basicAttribs = basicView.readAttributes();
And that's it. Now you have all of the basic file attributes for that file to do whatever you wish with. For theBasicFileAttributes, only the creation, last-modified, and last-access times can be altered (because it wouldn't make sense to change the size or type of file). To change these, we can use thejava.nio.file.attribute.FileTimeclass to create a new time and then call thesetTimes()method onBasicFileAttributeView. For example, we could move the last-modified time for our file a minute further into the future:
FileTime newModTime
= FileTime.fromMillis(basicAttribs.lastModifiedTime().toMillis() + 60000);
basicView.setTimes(newModTime, null, null);
The twonulls indicate that we do not want to change the last access time or creation time for this file. If you check the basic attributes again, in the same way we did earlier, you should see that the last modified time has been altered but the creation time and last access time have remained the same.
DosFileAttributeViewandDosFileAttributes
This view class allows you to get attributes specific to DOS. (As you might guess, this view is for Windows systems only.) ItsreadAttributes()method returns aDosFileAttributesinstance containing details of whether the file in question is read-only, hidden, a system file, and an archive. The view also hasset*(boolean)methods for each of these properties.
FileOwnerAttributeViewandUserPrincipal
This view class allows you to get and set the owner of a particular file. ItsgetOwner()method returns aUserPrincipal(also in thejava.nio.file.attributepackage), which in turn has agetName()method returning aStringcontaining the owner's name. The view also provides asetOwner(UserPrincipal)method allowing you to change a file's owner. This view is available on all platforms.
FileStoreSpaceAttributeViewandFileStoreSpaceAttributes
This catchily named class allows you to get information about a particular file store. ItsreadAttributes()method returns aFileStoreSpaceAttributesinstance containing details of the total space, the unallocated space, and the usable space on the file store. This view is available on all platforms.
PosixFileAttributeViewandPosixFileAttributes
This view class, available on UNIX® systems only, allows you to get and set attributes specific to POSIX (Portable Operating System Interface). ItsreadAttributes()method returns aPosixFileAttributesinstance containing details of the owner, group owner, and file permissions for this file (those you would normally set using the UNIXchmodcommand). The view also providessetOwner(UserPrincipal),setGroup(GroupPrincipal), andsetPermissions(Set<PosixFilePermission>)methods to modify these attributes.
UserDefinedFileAttributeViewandString
This view class, available only on Windows, allows you to get and setextended attributeson files. These attributes are unlike the others in that they are just name-value pairs and can be set to anything you wish. This can be useful if you want to add some hidden metadata to a file without altering the file's content. The view provides alist()method that returns aListofStringnames of the extended attributes for the relevant file.
To get the contents of a particular attribute once you have its name, the view has asize(String name)method to return the size of the attribute's value and aread(String name, ByteBuffer dest)method to read the attribute value into theByteBuffer. The view also provides awrite(String name, ByteBuffer source)method to create or alter an attribute, and also adelete(String name)method to remove an existing attribute entirely.
This is probably the most interesting new attribute view because it allows you to add attributes with arbitraryStringnames andByteBuffervalues to files. That's right — its value is aByteBuffer, so you can store any binary data in there you want.
First, we'll get the attribute view:
UserDefinedFileAttributeView userView
= attribPath.getFileAttributeView(UserDefinedFileAttributeView.class);
To get a list of the user-defined attribute names for this file, we call thelist()method on the view:
List<String> attribList = userView.list();
Once we have a particular attribute name we wish to get the associated value for, we allocate aByteBufferof the right size for the value and then call the view'sread(String, ByteBuffer)method:
ByteBuffer attribValue = ByteBuffer.allocate(userView.size(attribName));
userView.read(attribName, attribValue);
attribValuenow contains whatever data was stored for that particular attribute. To set your own attribute, you simply need to create yourByteBufferand fill it with whatever data you wish and then call thewrite(String, ByteBuffer)method on the view:
userView.write(attribName, attribValue);
Writing an attribute either creates that attribute or overwrites an existing attribute with the same name.
And with that, we conclude the third and final example. Full example code demonstrating four of the attribute views (BasicFileAttributeView,FileOwnerAttributeView,FileStoreSpaceAttributeView, andUserDefinedAttributeView) is included in the sample codedownload.
Conclusion
In addition to the topics covered in this article are a number of other NIO.2 file APIs. With Java 7 comes the new ability to create, inspect, and modify symbolic links. There are also new classes allowing access to lower-level information about the file system, and even to supply providers (calledFileSystemandFileStore) to access any type of file system you wish.
In summary, NIO.2 gives Java developers a simple, consistent, and powerful set of APIs to interact with the file system. Its aim is to abstract away some of the complex platform-specific details of dealing with files and directories and to give programmers more power and flexibility, and it has achieved this well.
http://www.ibm.com/developerworks/java/library/j-nio2-1/index.html