Wednesday, July 6, 2011

File and Directory Operations with Java 7's Files Class

In the blog post Java SE 7 Brings Better File Handling than Ever to Groovy, I discussed how the addition of NIO.2 features in JDK 7 means better file handling for Groovy scripts. My particular focus in that post was on the characteristics of files and directories provided by the new NIO.2 classes and interfaces. In this post, I focus on the file operations supported by Java 7's NIO.2 Files class.

I quoted the Javadoc documentation for the Files class in my earlier post and I repeat those two quoted sentences here:
This class consists exclusively of static methods that operate on files, directories, or other types of files.

In most cases, the methods defined here will delegate to the associated file system provider to perform the file operations.

The methods on the Files class have descriptive names. For example, it is obvious what methods like Files.copy, Files.createDirectory, Files.createSymbolicLink, Files.delete, and Files.move are intended to do.

The Files class makes heavy use of an interface new to Java 7 via NIO.2: the Path interface. As can be seen when viewing the Files class API via Javadoc, via running javac -XPrint java.nio.file.Files, or via running javap java.nio.files.Files, the Path interface is expected by many of the methods on the Files class. I introduced some of the useful methods Path provides in my previous post.

Once one has access to an instantiation of Path, that Path's parent can be retrieved as another instance of Path. Similarly root paths, absolute paths, real paths, sibling paths, relative paths, and even sub-paths can be easily accessed from a starting Path instance. Perhaps the most difficult (and it's not really very difficult) part of starting to use the new Files class is knowing how to get an initial instance of Path.

The easiest way to acquire a Path instance if you already have a java.io.File handle is to call File's new toPath() method. Other approaches for acquiring instances of Path include passing parts of a desired path to FileSystem.getPath(String, String...) [usually use the instance of FileSystem provided by FileSystems.getDefault()], passing a single or multiple Strings representing parts of a path to the new Paths class and its Paths.get(String,String...) method, or passing a URI instance to the Paths.get(URI) method.

Although the names of the methods on the Files class are self-describing and the Javadoc comments do a good job of filling in any minor details and covering nuances of these methods, I show some examples here to quickly illustrate some of the features of the new File operations provided by the Files class. The next simple Groovy script (demoFilesOperations.groovy) does just this, demonstrating some key file/directory operations methods on the JDK 7 Files class.

demoFilesOperations.groovy
#!/usr/bin/env groovy
/**
 * demoFilesOperations.groovy
 *
 * Demonstrate some of the operations on files and directories provided by Java
 * SE 7 and its NIO.2 implementation. Specific focus is applied to the methods
 * of the java.nio.file.Files class and the java.nio.file.Path interface.
 */

import java.nio.file.Files
import java.nio.file.Paths

// 1. Acquire 'working directory' name to use as current directory.
def currentDirectoryName = System.getProperty("user.dir")

// 2. Convert 'working directory' name plus a subdirectory named 'playarea' into
//    an instance of Path
def playAreaPath = Paths.get(currentDirectoryName, "playarea")
def playAreaStr = playAreaPath.toString()

// 3. Create new subdirectory with name 'playarea'
def playAreaDirPath = Files.createDirectory(playAreaPath)

// 4. Create a temporary directory with prefix "dustin_"
def tempDirPath = Files.createTempDirectory("dustin_")

// 5. Create temporary files, one in the temporary directory just created and
//    one in the "root" temporary directory. Create them with slightly different
//    prefixes, but the same '.tmp' suffix.
def tempFileInTempDirPath = Files.createTempFile(tempDirPath, "Dustin1-", ".tmp")
def tempFilePath = Files.createTempFile("Dustin2-", ".tmp")

// 6. Create a regular file.
def regularFilePath = Files.createFile(Paths.get(playAreaStr, "Dustin.txt"))

// 7. Write text to newly created File.
import java.nio.charset.Charset
import java.nio.file.StandardOpenOption
Files.write(regularFilePath,
            ["To Be or Not to Be", "That is the Question"],
            Charset.defaultCharset(),
            StandardOpenOption.APPEND, StandardOpenOption.WRITE)

// 8. Make a copy of the file using the overloaded version of Files.copy
//    that expects two Paths.
def copiedFilePath =
   Files.copy(regularFilePath, Paths.get(playAreaStr, "DustinCopied.txt"))

// 9. Move (rename) the copied file.
import java.nio.file.StandardCopyOption
def renamedFilePath = Files.move(copiedFilePath,
                                 Paths.get(playAreaStr, "DustinMoved.txt"),
                                 StandardCopyOption.REPLACE_EXISTING)

// 10. Create symbolic link in 'current directory' to file in 'playarea'
def symbolicLinkPath = Files.createSymbolicLink(Paths.get("SomeoneMoved.txt"), renamedFilePath)

// 11. Create (hard) link in 'current directory' to file in 'playarea'
def linkPath = Files.createLink(Paths.get("TheFile.txt"), regularFilePath)

// 12. Clean up after myself: cannot delete 'playarea' directory until its
//     contents have first been deleted.
Files.delete(symbolicLinkPath)
Files.delete(linkPath)
Files.delete(regularFilePath)
Files.delete(renamedFilePath)
Files.delete(playAreaDirPath)

The above example is self-contained and cleans up after itself. Note that on many file systems the above script should be executed with administrator privileges for the creation of links to work properly. The NIO.2 additions to Java 7 include several new enums and interfaces that these enums implement for capturing options related to these various file/directory operations. I tried to use some of these in the Groovy script above for demonstration. I also added a lot of comments to make it clear what the script is doing. The script is another reminder of the prevalence of Path instances when using the new file I/O API.


Java 7's Files Relationship to File Class

The Javadoc for the Java 7 version of the old timer java.io.File class describes the relationship of that older class with Java 7's new java.nio.file.Files class:
The java.nio.file package defines interfaces and classes for the Java virtual machine to access files, file attributes, and file systems. This API may be used to overcome many of the limitations of the java.io.File class. The toPath method may be used to obtain a Path that uses the abstract path represented by a File object to locate a file. The resulting Path may be used with the Files class to provide more efficient and extensive access to additional file operations, file attributes, and I/O exceptions to help diagnose errors when an operation on a file fails.


Conclusion

Although the example in this post was written in Groovy, the new NIO.2 file APIs are standard Java and can, of course, be used in Java code or by applications and scripts written in other JVM-based languages. The new Java 7 Files class provides convenient and consistent one-stop shopping for the most common operations of files and directories. This post has attempted to demonstrate how easy it is to apply the new Files class and its static methods. However, even with two posts now talking about the Files class, I have still not covered all it has to offer. I expect the Files class and the rest of the Java 7 NIO.2 file APIs to be of particular value in writing Groovy scripts that process files and directories.

3 comments:

steve said...

I wonder why the design has changed since earlier builds of Java 7.

What was wrong with somePath.copyTo(otherPath) compared to the design with the static utility methods in Files?

@DustinMarx said...

What’s new in Java 7 – The (Quiet) NIO File Revolution provides a different perspective on the new file handling provided by Java 7.

Binh Nguyen said...

Thanks, nice post