Groovy has brought many characteristics normally associated with scripting languages to the JVM with features such as implicit compilation (seeming save and execute without explicit compilation), dynamic typing, no need for specifying classes and main functions, and elegant command line parameter handling. However, even Groovy has lacked some of the file handling niceties and power of some other scripting languages. It's not that file manipulation cannot be done with Groovy, but it has not seemed as powerful or easy to manipulate files in Groovy "natively" as it is in scripting languages like PHP, Perl, and especially the shell languages. The good news is that JDK 7 introduces a whole new file management API that is intended for Java, but of course significantly and consequentially enhances Groovy's file system handling capabilities.
Java 7 provides new NIO.2 (JSR 203) features. A dramatic change in Java as part of this NIO.2 inclusion is the availability of a new and more powerful Java File I/O API. In this post, I look at using some of these in Groovy to detect information about the file system and to process files. Although I am focusing on use of these new APIs within Groovy scripts, there are obviously available to standard Java as well.
In the posts JDK 7: New Interfaces, Classes, Enums, and Methods and Groovy Script for Comparing Javadoc Versions, I looked at using Groovy to identify Java constructs new to Java 7 documentation. To get an idea what is new from an NIO.2 perspective, I've slightly modified the script used in the latter post as shown in the next code listing.
#!/usr/bin/env groovy // diffJdkVersionsJavadocsForNioChanges.groovy def cli = new CliBuilder( usage: 'diffJdkVersionsJavadocs -o <version_number> -n <version_number>', header: '\nAvailable options (use -h for help):\n', footer: '<version_number> should be one of the following:\n3 (JDK 1.3)\n4 (JDK 1.4)\n5 (J2SE 5)\n6 (Java SE 6)\n7 (Java SE 7)') import org.apache.commons.cli.Option cli.with { h(longOpt: 'help', 'Usage Information', required: false) o(longOpt: 'old', 'Old Version', args: 1, required: true, type: Integer) n(longOpt: 'new', 'New Version', args: 1, required: true, type: Integer) } def opt = cli.parse(args) if (!opt) return if (opt.h) cli.usage() @Grab(group='org.ccil.cowan.tagsoup', module='tagsoup', version='0.9.7') def versionMap = [3 : "JDK 1.3", 4 : "JDK 1.4", 5 : "J2SE 5", 6 : "Java SE 6", 7 : "Java SE 7"] def javadocMap = [(versionMap.get(7)) : "http://download.oracle.com/javase/7/docs/api/allclasses-frame.html", (versionMap.get(6)) : "http://download.oracle.com/javase/6/docs/api/allclasses-frame.html", (versionMap.get(5)) : "http://download.oracle.com/javase/1.5.0/docs/api/allclasses-frame.html", (versionMap.get(4)) : "http://download.oracle.com/javase/1.4.2/docs/api/allclasses-frame.html", (versionMap.get(3)) : "http://download.oracle.com/javase/1.3/docs/api/allclasses-frame.html"] def oldVersion = extractVersionStringFromChoice(opt.o, versionMap.get(6), versionMap) def newVersion = extractVersionStringFromChoice(opt.n, versionMap.get(7), versionMap) def first = javadocMap.get(newVersion) def firstDescription = newVersion def second = javadocMap.get(oldVersion) def secondDescription = oldVersion def firstXml = new XmlParser(new org.ccil.cowan.tagsoup.Parser()).parse(first) def firstUrls = firstXml.'**'.a.@href def secondXml = new XmlParser(new org.ccil.cowan.tagsoup.Parser()).parse(second) def secondUrls = secondXml.'**'.a.@href println "${firstDescription} URLs found: ${firstUrls.size()}" println "${secondDescription} URLs found: ${secondUrls.size()}" compareSetsOfStrings(secondUrls, secondDescription, firstUrls, firstDescription) /** * Extract a version String from the provided Integer-based choice. If the * provided Choice is not an Integer, the provided default String is returned. * * @param userChoice Choice that should resolve to an Integer. * @param defaultString String to be returned if provided Choice is not an * Integer. * @param versions Mapping of integers to version Strings. * * @return Version string. */ def String extractVersionStringFromChoice( Object userChoice, String defaultString, Map<Integer, String> versions) { def choice = 0 try { choice = userChoice != null ? userChoice as Integer : 0 } catch (NumberFormatException nfe) { choice = 0 println "'${userChoice}' is not and cannot be converted to an Integer; using '${defaultString}'" } def versionString = (choice < 3 || choice > 7) ? defaultString : versions.get(choice) return versionString } /** * Compare first Collection of Strings to second Collection of Strings by * identifying which Strings are in each Collection that are not in the other. * * @param firstStrings First Collection of Strings to be compared. * @param firstDescription Description of first Collection of Strings. * @param secondStrings Second Collection of Strings to be compared. * @param secondDescription Description of second Collection of Strings. */ def void compareSetsOfStrings( Collection<String> firstStrings, String firstDescription, Collection<String> secondStrings, String secondDescription) { println "Constructs in ${firstDescription} But Not in ${secondDescription}" def firstButNotSecond = firstStrings - secondStrings printIndentedStrings(firstButNotSecond) println "Constructs in ${secondDescription} But Not in ${firstDescription}" def secondButNotFirst = secondStrings - firstStrings printIndentedStrings(secondButNotFirst) } /** * Print the provided Strings one per line indented; prints "None" if the * provided List of Strings is empty or null. * * @param strings The Strings to be printed */ def void printIndentedStrings(Collection<String> strings) { if (!strings?.isEmpty()) { new TreeSet(strings).each { // LOOK HERE!!!: only change required for NIO-specific handling! if (it.contains("nio")) { println "\t${it}" } } } else { println "\tNone" } }
When the above Groovy script is executed, we see a listing of changes to Java constructs existing in packages that include the substring "nio" and have "1.7" somewhere in their Javadoc documentation. In other words, these are additions to NIO in Java 7. There are 93 affected Javadoc HTML files returned that are "nio" (one was a match on non-nio "union"). They are listed next.
- java/nio/channels/AcceptPendingException.html
- java/nio/channels/AlreadyBoundException.html
- java/nio/channels/AsynchronousByteChannel.html
- java/nio/channels/AsynchronousChannel.html
- java/nio/channels/AsynchronousChannelGroup.html
- java/nio/channels/AsynchronousFileChannel.html
- java/nio/channels/AsynchronousServerSocketChannel.html
- java/nio/channels/AsynchronousSocketChannel.html
- java/nio/channels/CompletionHandler.html
- java/nio/channels/IllegalChannelGroupException.html
- java/nio/channels/InterruptedByTimeoutException.html
- java/nio/channels/MembershipKey.html
- java/nio/channels/MulticastChannel.html
- java/nio/channels/NetworkChannel.html
- java/nio/channels/ReadPendingException.html
- java/nio/channels/SeekableByteChannel.html
- java/nio/channels/ShutdownChannelGroupException.html
- java/nio/channels/WritePendingException.html
- java/nio/channels/spi/AsynchronousChannelProvider.html
- java/nio/file/AccessDeniedException.html
- java/nio/file/AccessMode.html
- java/nio/file/AtomicMoveNotSupportedException.html
- java/nio/file/ClosedDirectoryStreamException.html
- java/nio/file/ClosedFileSystemException.html
- java/nio/file/ClosedWatchServiceException.html
- java/nio/file/CopyOption.html
- java/nio/file/DirectoryIteratorException.html
- java/nio/file/DirectoryNotEmptyException.html
- java/nio/file/DirectoryStream.Filter.html
- java/nio/file/DirectoryStream.html
- java/nio/file/FileAlreadyExistsException.html
- java/nio/file/FileStore.html
- java/nio/file/FileSystem.html
- java/nio/file/FileSystemAlreadyExistsException.html
- java/nio/file/FileSystemException.html
- java/nio/file/FileSystemLoopException.html
- java/nio/file/FileSystemNotFoundException.html
- java/nio/file/FileSystems.html
- java/nio/file/FileVisitOption.html
- java/nio/file/FileVisitResult.html
- java/nio/file/FileVisitor.html
- java/nio/file/Files.html
- java/nio/file/InvalidPathException.html
- java/nio/file/LinkOption.html
- java/nio/file/LinkPermission.html
- java/nio/file/NoSuchFileException.html
- java/nio/file/NotDirectoryException.html
- java/nio/file/NotLinkException.html
- java/nio/file/OpenOption.html
- java/nio/file/Path.html
- java/nio/file/PathMatcher.html
- java/nio/file/Paths.html
- java/nio/file/ProviderMismatchException.html
- java/nio/file/ProviderNotFoundException.html
- java/nio/file/ReadOnlyFileSystemException.html
- java/nio/file/SecureDirectoryStream.html
- java/nio/file/SimpleFileVisitor.html
- java/nio/file/StandardCopyOption.html
- java/nio/file/StandardOpenOption.html
- java/nio/file/StandardWatchEventKind.html
- java/nio/file/WatchEvent.Kind.html
- java/nio/file/WatchEvent.Modifier.html
- java/nio/file/WatchEvent.html
- java/nio/file/WatchKey.html
- java/nio/file/WatchService.html
- java/nio/file/Watchable.html
- java/nio/file/attribute/AclEntry.Builder.html
- java/nio/file/attribute/AclEntry.html
- java/nio/file/attribute/AclEntryFlag.html
- java/nio/file/attribute/AclEntryPermission.html
- java/nio/file/attribute/AclEntryType.html
- java/nio/file/attribute/AclFileAttributeView.html
- java/nio/file/attribute/AttributeView.html
- java/nio/file/attribute/BasicFileAttributeView.html
- java/nio/file/attribute/BasicFileAttributes.html
- java/nio/file/attribute/DosFileAttributeView.html
- java/nio/file/attribute/DosFileAttributes.html
- java/nio/file/attribute/FileAttribute.html
- java/nio/file/attribute/FileAttributeView.html
- java/nio/file/attribute/FileOwnerAttributeView.html
- java/nio/file/attribute/FileStoreAttributeView.html
- java/nio/file/attribute/FileTime.html
- java/nio/file/attribute/GroupPrincipal.html
- java/nio/file/attribute/PosixFileAttributeView.html
- java/nio/file/attribute/PosixFileAttributes.html
- java/nio/file/attribute/PosixFilePermission.html
- java/nio/file/attribute/PosixFilePermissions.html
- java/nio/file/attribute/UserDefinedFileAttributeView.html
- java/nio/file/attribute/UserPrincipal.html
- java/nio/file/attribute/UserPrincipalLookupService.html
- java/nio/file/attribute/UserPrincipalNotFoundException.html
- java/nio/file/spi/FileSystemProvider.html
- java/nio/file/spi/FileTypeDetector.html
- javax/lang/model/type/UnionType.html
As the above shows, there is significant new NIO functionality in Java 7. In the remainder of this post, I demonstrate using a subset of this new functionality from within Groovy.
Most of the Java 7/NIO.2 goodies discussed in this post reside within the new java.nio.file package. That is the case for two new classes available in Java 7 for dealing with the file system that are called FileSystems and FileSystem.
The next code listing contains Groovy code that invokes FileSystems.getDefault() to get an instance of
FileSystem
representing the default file system. The Groovy script then uses that returned representation of the default file system to ascertain the file systems' protocol, installed providers, stores, root directories, and supported attribute views.listNio2FileSystemsInfo.groovy
#!/usr/bin/env groovy /** * listNio2FileSystemsInfo.groovy */ import java.nio.file.FileSystems def fs = FileSystems.getDefault() println "Provider Scheme: ${fs.provider().scheme}" println "Installed Providers:" fs.provider().installedProviders().each { println "\t${it}" } println "File Stores:" fs.fileStores.each { println "\t${it}" } println "Root Directories:" fs.rootDirectories.each { println "\t${it}" } // There are several possible AttributeViews for file attributes. // acl - Access Control Lists // basic - Basic file attributes (mandatory and optional) // dos - Legacy DOS attributes // owner - Read/Update file owner // posix - Portable Operating System Interface (POSIX) attributes // user - User-defined attributes println "Supported File Attributes:" fs.supportedFileAttributeViews().each { println "\t${it}" }
The script above is very simple with several lines of the already-small script being comments. This small script leads to output like that shown in the next screen snapshot.
The above script and its output show how easy it is to acquire information about a specific file system. It is just as easy to garner details about individual files and directories on that file system using other classes and interfaces supplied in the
java.nio.file
package. The script listNio2FileAttributes.groovy
demonstrates significant utility provided by NIO.2 in Java 7 for reading file characteristics. After listing the entire code for that script, I focus on pieces of that script with their corresponding output.listNio2FileAttributes.groovy
#!/usr/bin/env groovy /** * listNio2FileAttributes.groovy <<filename>> * * List file attributes of provided file using Java NIO.2. The file to be * analyzed should be passed by name as the first argument to this script. */ @groovy.transform.Field HEADER_LINE = "*".multiply(75) import java.nio.file.FileSystems def file = new File(args[0]) def path = file.toPath() printPathBasics(path) printFilesBasics(path) printFileAttributeViews(path) import java.nio.file.Path /** * Print the basic attributes of the Path directly accessed on the provided Path. * If the provided Path is not absolute (such as a local file without directory * or folder in the path) then there will not be a parent or root directly * accessible, but these can be acquired by first converting to a real path or * to an absolute path and then requesting the parent and root from the real or * absolute path. * * @param path Path from which basic attributes are extracted. */ def void printPathBasics(Path path) { printHeader("Path Basics") println "toString: ${path}" println "File Name: ${path.fileName}" println "URI: ${path.toUri()}" println "Parent: ${path.parent}" println "Root: ${path.root}" println "Absolute?: ${path.absolute}" def absolutePath = path.toAbsolutePath() println "Absolute: ${absolutePath}" println "\tParent: ${absolutePath.parent}" println "\tRoot: ${absolutePath.root}" def realPath = path.toRealPath() println "Real: ${realPath}" println "\tParent: ${realPath.parent}" println "\tRoot: ${realPath.root}" } import java.nio.file.Files /** * Print the basic attributes of the Path provided via methods on the Files * class. * * @param path Path from which attributes available via Files class are to be * extracted. */ def void printFilesBasics(Path path) { printHeader("Files Basics") println "Size: ${Files.size(path)} bytes" println "Owner: ${Files.getOwner(path)}" println "Exists? ${Files.exists(path) ? 'yes' : 'no'}" println "Not Exists? ${Files.notExists(path) ? 'yes' : 'no'}" println "Regular File? ${Files.isRegularFile(path) ? 'yes' : 'no'}" println "Readable? ${Files.isReadable(path) ? 'yes' : 'no'}" println "Executable? ${Files.isExecutable(path) ? 'yes' : 'no'}" println "Directory? ${Files.isDirectory(path) ? 'yes' : 'no'}" println "Is Hidden? ${Files.isHidden(path) ? 'yes' : 'no'}" println "Is Symbolic Link? ${Files.isSymbolicLink(path) ? 'yes' : 'no'}" println "Last Modified Time: ${Files.getLastModifiedTime(path)}" } /** * Print the various file attribute views for the provided Path. * * @param path Path for which file attribute views are to be printed. */ def void printFileAttributeViews(Path path) { def views = FileSystems.default.supportedFileAttributeViews() if (views.contains("acl")) { printAclAttributes(path) } if (views.contains("basic")) { printBasicAttributes(path) } if (views.contains("dos")) { printDosAttributes(path) } if (views.contains("owner")) { printOwnerAttributes(path) } if (views.contains("user")) { printUserAttributes(path) } } /** * Print Access Control List attributes for provided Path. * * @path Path for which ACL attributes are desired. */ import java.nio.file.attribute.AclFileAttributeView def void printAclAttributes(Path path) { printHeader("Access Control List Attributes") def aclView = Files.getFileAttributeView(path, AclFileAttributeView.class) aclView.acl.each { entry -> println "${entry.principal().name} (${entry.type})" entry.permissions().each { permission -> println "\t${permission}" } } } /** * Print basic file attributes for provided Path. * * @param Path for which basic attributes are desired. */ import java.nio.file.attribute.BasicFileAttributeView def void printBasicAttributes(Path path) { printHeader("Basic File Attributes") def basicView = Files.getFileAttributeView(path, BasicFileAttributeView.class) def basicAttributes = basicView.readAttributes() basicAttributes.each { println "Creation Time: ${it.creationTime()}" println "Modification Time: ${it.lastModifiedTime()}" println "Access Time: ${it.lastAccessTime()}" println "Directory? ${it.isDirectory() ? 'yes' : 'no'}" println "File? ${it.isRegularFile() ? 'yes' : 'no'}" println "Other? ${it.isOther() ? 'yes' : 'no'}" println "Symbolic Link? ${it.isSymbolicLink() ? 'yes' : 'no'}" println "Size: ${it.size()} bytes" } } /** * Print DOS file attributes for provided Path. * * @param Path for which DOS file attributes are desired. */ import java.nio.file.attribute.DosFileAttributeView def void printDosAttributes(Path path) { printHeader("DOS File Attributes") def dosView = Files.getFileAttributeView(path, DosFileAttributeView.class) def dosAttributes = dosView.readAttributes() dosAttributes.each { println "Archive? ${it.isArchive() ? 'yes' : 'no'}" println "Hidden? ${it.isHidden() ? 'yes' : 'no'}" println "Read-only? ${it.isReadOnly() ? 'yes' : 'no'}" println "System? ${it.isSystem() ? 'yes' : 'no'}" } } /** * Print file owner attributes for provided Path. * * @param path Path for which file owner attributes are desired. */ import java.nio.file.attribute.FileOwnerAttributeView def void printOwnerAttributes(Path path) { printHeader("Owner Attributes") def ownerView = Files.getFileAttributeView(path, FileOwnerAttributeView.class) println ownerView.owner } /** * Print user attributes for provided Path. * * @param path Path for which user attributes are desired. */ import java.nio.file.attribute.UserDefinedFileAttributeView def void printUserAttributes(Path path) { printHeader("User Attributes") def userView = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class) userView.list().each { println it } } /** * Print Header for section of output. * * @param headerTitle String to be included in output header. */ def void printHeader(String headerTitle) { println "\n${HEADER_LINE}" println "* ${headerTitle}" println "${HEADER_LINE}\n" }
The main body of the script is simple and that snippet is shown here:
import java.nio.file.FileSystems def file = new File(args[0]) def path = file.toPath() printPathBasics(path) printFilesBasics(path) printFileAttributeViews(path)
Most of the above main script body snippet is calling functions defined elsewhere in the script. However, it is worth noting that
FileSystems
is imported here and that a new Java 7 method on the old Java class java.io.File is now available. The File.toPath() method allows for each conversion from an old timer File instance to a new Java 7 java.nio.file.Path instance. The Java 7 NIO.2 APIs tend to favor Path, so this conversion is useful.The three functions defined elsewhere in this script nicely categorize the types of data we can retrieve related to a particular file. I look at each of these categories next.
Path Characteristics
The first category of file details that Java 7 NIO.2 makes available is basic Path information. The following snippet of code contains my function called
printPathBasics
that accepts a java.nio.file.Path
(in this case the one returned by the just mentioned File.toPath()
method) and displays the basic information available directly from that Path
instance. Because this is Groovy, I have the luxury of importing java.nio.file.Path
just before I use it.import java.nio.file.Path /** * Print the basic attributes of the Path directly accessed on the provided Path. * If the provided Path is not absolute (such as a local file without directory * or folder in the path) then there will not be a parent or root directly * accessible, but these can be acquired by first converting to a real path or * to an absolute path and then requesting the parent and root from the real or * absolute path. * * @param path Path from which basic attributes are extracted. */ def void printPathBasics(Path path) { printHeader("Path Basics") println "toString: ${path}" println "File Name: ${path.fileName}" println "URI: ${path.toUri()}" println "Parent: ${path.parent}" println "Root: ${path.root}" println "Absolute?: ${path.absolute}" def absolutePath = path.toAbsolutePath() println "Absolute: ${absolutePath}" println "\tParent: ${absolutePath.parent}" println "\tRoot: ${absolutePath.root}" def realPath = path.toRealPath() println "Real: ${realPath}" println "\tParent: ${realPath.parent}" println "\tRoot: ${realPath.root}" }
The names of the file characteristics available directly from
Path
are fairly self-explanatory based on their well-chosen names. The characteristics include things like the path as provided, the "absolute path", the "real path", the path's file name, the path's parent, the path's root, and the path's URI.The output is most interesting for differentiating between path, real path, and absolute path by using various examples of different types of paths, so the following screen snapshots will do just that. One file that will be run against each portion of the script will be provided with its absolute path (C:\Users\Dustin\dustinOutput.xls), with a relative path (..\..\..\..\Users\Dustin\dustinOutput.xls), with a hard link named hardlink.xls, and with a soft link named softlink.xls.
As a side note, the hard and soft links are typically created with the ln command for Linux (with -s option for soft links and no option for hard links). In Windows/DOS, the mklink command is typically used (with /H for hard links and no option for soft links). The Windows approach (mklink) for the files in question in this post is shown in the next screen snapshot.
Because I created the links in a "links" subdirectory, their use will demonstrate both links and relative directories (subdirectory in this case) in action. The next series of screen snapshots demonstrate the last covered Groovy code executed against the four paths (absolute path, relative path, hard link, and soft link) pointing to the same file.
Basic Path Information: Absolute Path Provided
Basic Path Information: Relative Path Provided
Basic Path Information: Hard Link Path Provided
Basic Path Information: Soft Link Path Provided
Some observations can be made from comparing the output shown immediately above. First, there are sometimes differences between "absolute path" and "real path" (which are documented in the Javadoc, by the way). As the output shows, the "absolute" path version of a provided relative path includes the "relative portions" in it. The "real" path, on the other hand, removes any redundancies to leave the cleanest and shortest possible full path. The path returned for soft links differs between "real" and "absolute" paths as well: the "real" path is again the cleanest and returns the actual path pointed to by the soft link while the "absolute" soft link path provides the full path of the link and not its target. A third observation is that the absolute path was the only one of the four types of provided paths for which a direct "root" was obtained. The others required going to their parent to get the root.
Files Characteristics
Another new class introduced by Java 7 NIO.2 is the Files class. Its Javadoc documentation describes it quite well: "This class consists exclusively of static methods that operate on files, directories, or other types of files." The documentation further explains a dependency on the file system: "In most cases, the methods defined here will delegate to the associated file system provider to perform the file operations."
The next code listing contains another snippet of code from the above script and shows how significant details regarding a particular file or directory can be obtained easily using the static methods on the new
Files
class. Nuggets of information regarding the provided Path
instance include things like file size (in bytes), file owner, indication of file or directory, whether it's executable, whether it's hidden, whether it's a symbolic link, and last modified date/time.import java.nio.file.Files /** * Print the basic attributes of the Path provided via methods on the Files * class. * * @param path Path from which attributes available via Files class are to be * extracted. */ def void printFilesBasics(Path path) { printHeader("Files Basics") println "Size: ${Files.size(path)} bytes" println "Owner: ${Files.getOwner(path)}" println "Exists? ${Files.exists(path) ? 'yes' : 'no'}" println "Not Exists? ${Files.notExists(path) ? 'yes' : 'no'}" println "Regular File? ${Files.isRegularFile(path) ? 'yes' : 'no'}" println "Readable? ${Files.isReadable(path) ? 'yes' : 'no'}" println "Executable? ${Files.isExecutable(path) ? 'yes' : 'no'}" println "Directory? ${Files.isDirectory(path) ? 'yes' : 'no'}" println "Is Hidden? ${Files.isHidden(path) ? 'yes' : 'no'}" println "Is Symbolic Link? ${Files.isSymbolicLink(path) ? 'yes' : 'no'}" println "Last Modified Time: ${Files.getLastModifiedTime(path)}" }
The next series of screen snapshots demonstrates running the portion of the script just shown against the four types of path previously covered (absolute, relative, hard link, and soft link). I throw in a fifth screen snapshot running against an executable file and a sixth screen snapshot running against a directory path that is owned by the Administrative user.
Basic File Information: Absolute Path
Basic File Information: Relative Path
Basic File Information: Hard Link Path
Basic File Information: Soft Link Path
Basic File Information: Executable Path
Basic File Information: Administrator-Owned Directory
I have looked at the new Files class from the perspective of reading characteristics of files and directories. The class supports much more than that, including creation of directories and files, copying directories and files, moving/renaming directories and files, and accessing POSIX-compliant file and directory permissions. The additional methods are so numerous that I plan to cover many of them in a separate post rather than add to this already long post.
File Attributes Views
Each file system supports certain file attributes views, although all are required to support the basic file attributes view. The example toward the beginning of this post that called FileSystem.supportedFileAttributeViews() showed which views are supported on the particular machine I'm using: acl, basic, dos, owner, and user. Because it's a Windows machine, it's not surprising that it does NOT support the posix attributes view.
This code snippet from the overall script is longer because it includes six methods: one that is an overall method for handling file attributes views and one each for the five supported views. Note that once again the
Path
is the key to use of these APIs./** * Print the various file attribute views for the provided Path. * * @param path Path for which file attribute views are to be printed. */ def void printFileAttributeViews(Path path) { def views = FileSystems.default.supportedFileAttributeViews() if (views.contains("acl")) { printAclAttributes(path) } if (views.contains("basic")) { printBasicAttributes(path) } if (views.contains("dos")) { printDosAttributes(path) } if (views.contains("owner")) { printOwnerAttributes(path) } if (views.contains("user")) { printUserAttributes(path) } } /** * Print Access Control List attributes for provided Path. * * @path Path for which ACL attributes are desired. */ import java.nio.file.attribute.AclFileAttributeView def void printAclAttributes(Path path) { printHeader("Access Control List Attributes") def aclView = Files.getFileAttributeView(path, AclFileAttributeView.class) aclView.acl.each { entry -> println "${entry.principal().name} (${entry.type})" entry.permissions().each { permission -> println "\t${permission}" } } } /** * Print basic file attributes for provided Path. * * @param Path for which basic attributes are desired. */ import java.nio.file.attribute.BasicFileAttributeView def void printBasicAttributes(Path path) { printHeader("Basic File Attributes") def basicView = Files.getFileAttributeView(path, BasicFileAttributeView.class) def basicAttributes = basicView.readAttributes() basicAttributes.each { println "Creation Time: ${it.creationTime()}" println "Modification Time: ${it.lastModifiedTime()}" println "Access Time: ${it.lastAccessTime()}" println "Directory? ${it.isDirectory() ? 'yes' : 'no'}" println "File? ${it.isRegularFile() ? 'yes' : 'no'}" println "Other? ${it.isOther() ? 'yes' : 'no'}" println "Symbolic Link? ${it.isSymbolicLink() ? 'yes' : 'no'}" println "Size: ${it.size()} bytes" } } /** * Print DOS file attributes for provided Path. * * @param Path for which DOS file attributes are desired. */ import java.nio.file.attribute.DosFileAttributeView def void printDosAttributes(Path path) { printHeader("DOS File Attributes") def dosView = Files.getFileAttributeView(path, DosFileAttributeView.class) def dosAttributes = dosView.readAttributes() dosAttributes.each { println "Archive? ${it.isArchive() ? 'yes' : 'no'}" println "Hidden? ${it.isHidden() ? 'yes' : 'no'}" println "Read-only? ${it.isReadOnly() ? 'yes' : 'no'}" println "System? ${it.isSystem() ? 'yes' : 'no'}" } } /** * Print file owner attributes for provided Path. * * @param path Path for which file owner attributes are desired. */ import java.nio.file.attribute.FileOwnerAttributeView def void printOwnerAttributes(Path path) { printHeader("Owner Attributes") def ownerView = Files.getFileAttributeView(path, FileOwnerAttributeView.class) println ownerView.owner } /** * Print user attributes for provided Path. * * @param path Path for which user attributes are desired. */ import java.nio.file.attribute.UserDefinedFileAttributeView def void printUserAttributes(Path path) { printHeader("User Attributes") def userView = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class) userView.list().each { println it } }
There is some redundancy in these file attributes views in that they provide some of the same details as are discoverable directly on a
Path
via the Path
itself or via the static Files
methods acting on the Path
. For example, when the absolute path is run against this portion of the script, its basic file attributes view and dos file attributes view contain much of the same characteristics we've already seen.The output above demonstrates that we can get file creation time and file access time from the basic file attributes in addition to modification time. Most of the other information in the basic file attributes view is available directly from
Path
or from Files
applied to a Path
. The dos file attributes view tells us whether the file is hidden, whether it's archive, whether it's system, and whether it's read-only.The file attributes view that I find most interesting for new details in the Access Controls List view. The output for this view run on the absolute path is shown next.
This is a pretty granular level of detail regarding the Access Control List.
The final screen snapshot shows information returned for the file under the owner view and the user attributes view.
Groovy's File-Handling Alternatives
Even before the availability of Java 7 NIO.2 additions, Groovy offered several approaches for working with file systems. These included using Java's older file I/O (such as
File
class), using some Groovy GDK extensions to Java's file I/O classes, using the underlying operating systems' commands, and using AntBuilder. The new Java 7 NIO.2 additions, however, provide more power, potential performance, and standardization than ever before for Groovy file handling.Conclusion
This post has demonstrated a small portion of the Java 7 NIO.2 additions with coverage of a small number of handy classes and interfaces. I hope to cover some of my other favorite new features in later posts, but this post has covered some of the basics that I believe will lead to better and more efficient Groovy scripts that make use of files and file systems. Of course, not just Groovy will benefit. Other JVM languages and, of course, Java itself should also benefit from a new and improved file handling API.
2 comments:
Hi Dustin !
Thats a great post, pretty helpful.
I was wondering if you would know if to set a directory to read-only mode on Windows 7 using Java SE7. Any help on this would be greatly appreciated.
Thanks,
RTA
Hi Dustin,
Thank you for your excellent blog, I'm trying to learn Groovy and you've been a great help to me.
I have a question about printing the basic file attributes. What is the purpose of the basicView object?
I find that I can set up to print the file attributes with just one assigment:
attr = Files.readAttributes(filePath, BasicFileAttributes.class)
without the need to refeer to the BasicFileAttributeView class.
Is this poor practice and if so, why?
Many thanks,
Jeremy
Post a Comment