Thứ Tư, 6 tháng 7, 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.

Thứ Ba, 5 tháng 7, 2011

Java SE 7 Brings Better File Handling than Ever to Groovy

There have always been multiple reasons to avoid Java for writing scripts. It was not surprising that Java was not the most appropriate language for scripting because it was never intended to be a scripting language. In addition, the Write Once Run Anywhere feature of Java that was of benefit in so many cases was a disadvantage when it came to platform-specific functionality, including file input/output and file system management.

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.

Thứ Hai, 4 tháng 7, 2011

Standard Java/Groovy Directory Properties

I mostly use Groovy for scripting and many of my scripting needs require the ability to navigate directory structures. In writing such scripts, it is helpful to know which properties are available out of the box for the Groovy script developer. This short post summarizes some of the most common properties available to a Groovy script via Java standard Properties and Groovy's own property.

Java provides several useful properties for determining directories. These include user.dir (user directory AKA working directory or current directory), user.home (user's home directory), java.io.tmpdir (directory for temporary files), and java.home (base directory of applicable JRE installation, often corresponding to $JAVA_HOME or %JAVA_HOME% environment variables).

Groovy adds it own useful directory property with groovy.home, a property which points to the base directory of the applicable Groovy installation. In other words, groovy.home is to the Groovy installation what java.home is to the Java installation. The following simple Groovy script shows each of these properties in action.

demoJavaPlusGroovyDirectoryProperties.groovy
#!/usr/bin/env groovy
/**
* demoJavaPlusGroovyDirectoryProperties.groovy
*/
println "User Directory (user.dir):\n\t${System.getProperty('user.dir')}"
println "Current Directory (.):\n\t${new File(".").canonicalPath}"
println "User Home (user.home):\n\t${System.getProperty('user.home')}"
println "Java Home (java.home):\n\t${System.getProperty('java.home')}"
println "Groovy Home (groovy.home):\n\t${System.getProperty('groovy.home')}"
println "Temporary Directory (java.io.tmpdir):\n\t${System.getProperty('java.io.tmpdir')}"

The next screen snapshot shows the above script being run twice from its own containing directory and from one directory higher than that. As the output from running the script twice shows, the current directory provided by "." matches that provided by the property user.dir in both cases.


Access to these properties within Groovy scripts can be useful in a variety of contexts. Scripts can use this information to know where files are placed for standard situations and to provide this information in script output to the user. Use of these properties can also reduce hard-coded directories within the script.

Adding Line Numbers to File with Groovy's -p and -e Options

A commonly used example to demonstrate Groovy's groovy launcher -e and -p options is adding line numbers to a specified text file. Not only does this demonstrate the -e and -p options well, but its a useful little tool because there are often times when we want to quickly see a line number in a file for reference. In this post, I look at a couple variations of this common example.

The Groovy CLI section of the Groovy User Guide offers several examples of using command-line Groovy with the options -i, -e, and -p. Some of these examples include adding line numbers to a text file. For example, the page currently includes this example:

groovy -pi .bak -e "count + ': ' + line"

The -i option tells Groovy to modify the supplied file in place and the .bak specified after that tells Groovy to create a backup file with the .bak extension. However, for my first version of this, I'm going to use a slightly modified version without modifying in place and thus without specifying a backup file extension. Here is the revised version:

groovy -p -e "count + ': ' + line"

To demonstrate the use of the above, I will use a simple Java class called TimeZoneIDs.java, whose code listing is shown next.

TimeZoneIDs.java
package dustin.examples;

import java.util.Arrays;
import java.util.TimeZone;
import static java.lang.System.out;

public class TimeZoneIds
{
public static void main(final String[] arguments)
{
out.println(Arrays.toString(TimeZone.getAvailableIDs()));
}
}

To use the simple command-line Groovy command to add line numbers to the above listed Java source code, I simply type (in DOS) or cat (in Linux) the file and pipe it to the Groovy command shown above. This is demonstrated in the next screen snapshot.


All one would need to do at this point is add a redirection (>) to this output and redirect it to any desired file.

The "count" in the above example was implicitly available to the Groovy launcher and represented the count of the line currently being processed. In other words, by virtue of using the -p option to process and print the contents on a per line basis, the "count" variable was implicitly available as a BigInteger. Likewise, the "line" variable is also available implicitly and is a handle to the String containing the current line itself that is being processed.

The one downside to the above is that the number of digits in the line numbers changes and this makes the spacing a little off after line 9. This can be easily remedied with a minor addition to the above command.

groovy -p -e "count.toString().padLeft(2) + ': ' + line"

The next screen snapshot demonstrates that running this version more nicely aligns the text of the file as long as the number of lines does not go into three digits.


A disadvantage of the last example is that the spacing can still be off if there are more lines than anticipated and specified to the String.padLeft call. One way to deal with this is to pick a very high number of characters to pad to. Another approach would be to first determine the number of lines of code and then set the padding appropriately. One can also choose to use the overloaded version of the GDK's String.padLeft to pass a different padding character than space (such as "0").

One approach that can be used to find the number of lines in the file via Groovy and the -e option is shown in the next example (against the TimeZoneIds.java class in this particular case).

groovy -e "println new File('TimeZoneIds.java').readLines().size()"

When the above is run on my example, it returns the integer 13. That tells me that 2 places should be sufficient for the number of digits in the largest line number. This is also another Groovy one-liner!

Another thing to note is that you should typically specify -p or -n before specifying -e because -e expects the Groovy script in quotes as a parameter that goes with it. Alternatively, you can combine them and use -pe as demonstrated in the next screen snapshot.


Adding line numbers to a text file is another example of a Groovy one-liner that is small enough that it doesn't even need a script file and can be used with Groovy's -e and -p options. Another good reference for additional details on these options is Groovy's -e and friends: The Command Line for Java Developers.

Thứ Bảy, 2 tháng 7, 2011

Software Development Posts of Interest - 2 July 2011

There have been numerous blog posts on software development that I have found to be well worth the time invested in reading them in recent weeks. In this post, I summarize and link to some of these. Topics include Oracle's Java 7 kick-off, Scala, Devops, multiple posts on free software development ebooks and tutorials, a post of best Java books, and ideas on how to improve programming skills.


Introducing Java 7 Webcast: Moving Java Forward

Oracle is hosting a live webcast to celebrate Java 7. The event, also known as Best Party of 2011: Introducing Java 7, is scheduled for Thursday, 7 July 2011, beginning at 9 am Pacific Time. According to the posted agenda, there will be an hour on "Introducing Java 7" followed by 1/2 hour each on Project Coin, on the Fork/Join Framework, and the New File System API. There is a 25-minute section scheduled under the title "A Renaissance VM: One Platform, Many Languages." The conclusion is a little over an hour for the section "Meet the Experts: Q&A and Panel Discussion."

Registration for this live webcast is free. In addition to the webcast, this "global introduction" to Java 7 is also taking place in three physical locations around the globe: Redwood Shores (hosted by Adam Messinger), São Paulo (hosted by Bruno Souza), and London (hosted by Ben Evans). The Twitter conversation will be at #java7.

Oracle is known for its marketing and has doing things like adding "i" to their products' names/versions for "Internet" and later "g" for grid. It is no coincidence, of course, that this Java 7 introduction takes place on 7/7 this year.


Top Five Free Java Ebooks

Although I was already aware of the availability of the five Java ebooks recommended in the post Top 5 Free Java Ebooks, it provides a nice collection of them with nice images and concise descriptions. The covered books are (in my order rather than the author's) The Java Tutorials, Thinking in Java (Third Edition), The Java Language Specification, Introduction to Programming Using Java (Sixth Edition), and Core Servlets and Java Server Pages (Second Edition). I happen to really like the last-listed book, but it is less generally applicable than the others. However, for someone writing servlets and JSPs, it is a valuable resource as is the Java EE 6 Tutorial.

There are other interesting "best of" posts on this blog. These include Top 8 Java People You Should Know (I wasn't one of them!), Top 20 Java Web Sites You Must Visit (I use several of the recommended sites regularly and admired the fact that itself was listed as the twentieth "must visit" site), and 10 Java Regular Expression Patterns You Should Know.


Fifteen Free E-books Every Web Developer Should Have

My only complaint about hammad's post 15 Free E-Books Every Developer Should Have is that its title should reference "Web Developer" rather than just "Developer" because all fifteen listed resources are oriented specifically at web development. The 15 listed free ebooks cover a wide variety of web development topics such as HTML5, beginning JavaScript, web site best practices, Ajax, PHP, and JQuery. This post seems to build upon the post Ten Free Ebooks Every Web Developer Should Have that I previously highlighted.


Scala in 5 Years

Nikita Ivanov's blog post Scala in 5 Years - My Prediction is one of the best blog posts of its type that I've ever read (its sustained high rating on DZone/JavaLobby confirms that I'm not the only one who thinks it is excellent). By "best ... of its type," I mean that this post was one of the best posts I have read of the type that predict a trendy language's framework. It is excellent because it is written by someone who knows the trendy language well, but at the same time understands the "bigger picture" of the business world and the environment in which that language is competing for attention.

I have no idea if Ivanov's numbers are correct (and he's probably only using them to illustrate the point with a rough idea than prescribing any exactness to them), but I think his overall prediction is reasonable and perhaps even probable. The reason I am so quick to agree with his assessment is that his explanations match my own experience. In fact, I'm probably best classified as one of those who has been mildly curious about Scala, but who has never really invested time or energy into it because Java and Groovy together satisfy my most urgent development needs and leave me with little that I think I need to remedy with a new language.

I don't care much for blog posts that are 100% positive on something and dismiss everything else (a subset of Ruby on Rails and even Scala posts have been notorious for this). I similarly don't care for the 100% negative posts that simply say something sucks without evidence or that stretch some really unimportant negative issues past how bad they really are. This Scala in 5 Years post is so impressive because it is written by someone who does love and know Scala, but is able to remain impartial enough to give a realistic assessment of that language's future given the environment in which it competes. As is the case with most of the best posts, there is significant value to be gained from reading some of the feedback comments on the post itself as well as on its DZone link.

The funny thing is that although the post about Scala in 5 Years might be thought to have the effect of discouraging one from learning Scala, especially if he or she already knows Java and Groovy, the effect has almost been the opposite for me. The remarkably refreshing nature of a post like this makes Scala's benefits and advantages seem less mythical and more realistic and that may be something worth looking at more closely.


Should C++ Be Your Next Language?

I previously wrote that PHP is my next language because of several advantages it offers in today's web development world. Mike James has written a post Why your next language better be C++. C++ won't be my "next language" because I did use it professionally for five years and so don't consider it a "new" language for me. That being stated, I also realize that I'd be a bit rusty to return to C++ after not using it all for nearly five years now.

What particularly interested me about James's post is persuasiveness of his arguments for why C++ is a valuable language to learn for the future. Although he did not convince me that C++ will necessarily be the "language of my choice" in the future, I do think he has compelling arguments for why C++ will be a highly prevalent language for years to come. I'm not convinced that C++ has "won the war" against C# just yet. I'm also not convinced that the future is quite as clear as James states, but it is a provocative statement:
So the future is clear: there will be JavaScript in the browser and C++ will rule the rest.

I try to not let disagreement with a post be confused in my own mind with not appreciating the substance and quality of that post. In the case of this post, I have mixed feelings about James's arguments (some agreements and some disagreements), but I found it to be an interesting post that made me think about programming languages differently than I have in recent months. If nothing else, it reaffirms to me that C++ is here to stay.


Devops

I've been struggling with the term "devops" recently. It's not that I don't understand its basic essence. As is the case with many "movements" in the software development industry, I see many observations that the "devops" movement cites as valid observations that are issues software development projects regularly face. Likewise, as is also the case with many "movements" in our industry, I can see how some of the recommendations and tactics collected as part of the movement do seem to make some sense. However, what I struggle with is what I have always struggled with in relation to other software development movements: it is difficult for me to get my mind or hands around a tangible approach that embodies the movement. The agile movement has taken years to attempt to develop this and even today suffers from contradictory opinions and folks who have stretched it too thin to where it means just about anything one wants it to mean.

Ted Dziuba's post Devops is a Poorly Executed Scam and Gareth Rushgrove's Devops Isn't A Methodology are two interesting reads for someone trying to get a handle on the devops movement. Both are well-written with the former being a little more strongly worded and the latter being more restrained. Both authors may be correct even though the latter post is in response to the former post and comes at it with a different view.

I must admit to some of the same misgivings about devops that Ted Dziuba outlines in his post. Gareth Rushgrove's post did not clear up some of my concerns and questions regarding where this movement can go and what it can do in terms of tangible steps to improve the developer/operations impedance mismatch, but it did validate that many of the "common sense" approaches that predate "devops" are still valid answers. For instance, Dziuba proposes that an obvious tactic to reduce the issue is to deploy on the same machines and operating systems one develops on. Rushgrove agrees with this in his post (as do I).

As long as the "devops" movement is about collecting and articulating these "common sense" ideas, there is hope for it. Just as agile has collected and articulated many "common sense" ideas most of us saw in action before it was a movement, "devops" can do the same thing. For me, much of agile's value and much of devops's potential value are parallel to the rise of design patterns. Patterns of design existed long before the seminal Design Patterns book, but what that book and the concept of "design patterns" did was to collect, organize, and articulate these best design practices.

I like significant portions of agile that are based on proven experience with software development. I am much less fond of the money side of it. It will be interesting to see if the devops movement moves that way. For now, I still have not made my mind up about devops. There is no better evidence of this than the fact that I have not yet created a Devops tag for my blog posts and have not yet devoted a post exclusively to the subject.


Ten Ways to Improve Your Programming Skills

Anders Ahlström's blog post might at first glance seem like just another blog post reiterating things that developers can do to improve their programming skills. In many ways, it does repeat the same advice as is contained on numerous other sites, but there are some interesting twists. Besides the fact that the author is apparently 15 years old, I really liked his tenth recommendation: "Don’t rush to StackOverflow. Think!" He also recommends "writing about coding on a blog, even if it is just for yourself" (something I definitely agree with).

I generally agree with all ten recommendations. However, I also found some of the comments on this post to be equally informative and thought-provoking. There are useful comments on this post both on the post itself (78 comments currently) as well as the Reddit reference (225 comments currently) to it. One of the comments makes a valid point that customers and employers don't pay developers specifically to learn new things and that there are times when a developer should go to StackOverflow.com or other forum to get an answer quickly for the customer or employer. Another comment points out that sometimes StackOverflow provides a more sophisticated solution more quickly than can be hacked together. This is a good point and, if used properly, can lead to learning as well. I believe the trick is to balance the two: don't neglect any thought altogether, but also don't waste time reinventing the wheel.

The comments on this post (both the original post and the Reddit reference) provided terms and definitions of terms that are interesting in the general software development world. These include Parkinson's Law of Triviality (AKA bike shedding or bicycle shedding), Wadler's Law (we spend our time disproportionately on what's arguably least important in software development), and rubber ducking (stating problems to oneself or to an inanimate object to make the solution clearer).

Because the post 10 Ways to Improve Your Programming Skills listed one of the ten ways for improvement as "Hang out at programming sites and read blogs," it is not too surprising that respondent comments asked (and answered) what blogs are recommended. Although I was hugely disappointed to not see my blog listed, listed blogs included some new to me such as Existential Type, Inside 1712B, Neopythonic, A Neighborhood of Infinity, Reginald Braithwaite's homoiconic, ScottGu's Blog, and The Daily WTF.


Periodic Table of the HTML5 Elements

Josh Duck's Periodic Table of the Elements is an aesthetically pleasing presentation of proposed HTML5 elements in a fashion that appeals to the typical geeky side of many developers.


Java Developer Most Useful Books

The post Java Developer Most Useful Books lists several well-known books for Java developers. Besides the sizable list of good books in the post itself, readers have left comments with other good books on Java development.


Fifteen Best Resources to Learn PHP

The post 15 Best Resources to Learn PHP lists several resources I have found useful as I have begun investigating PHP plus many others that look useful.


Conclusion

The posts cited here either reference other sources of information useful for developers or are thought-provoking in their own right.

Thứ Ba, 28 tháng 6, 2011

Groovier Static Import

The introduction of the static import with J2SE 5 has provided some advantages. For example, I like it for specifying import static java.lang.System.out in my simple Java classes so that I can access the standard output stream easily with the out handle. (Cay Horstmann's Are you using static import? talks more about this use of static import.) In this blog post, I look at how Groovy supports Java's static import and adds more features to it to make it even more useful for concise and highly readable scripts.

The following simple Groovy script demonstrates use of Groovy's static import. One case prints out the value of Math.PI using both traditional Java regular import support and traditional Java static import support. The other examples are all Groovy and demonstrate how Groovy's static import even allows method calls to be referenced by constants defined with a Groovy static import. I attempted to place enough comments in this script to help show off Groovy's various static import features.

demonstrateGroovyStaticImport.groovy
#!/usr/bin/env groovy
/**
* demonstrateGroovyStaticImport.groovy
*
* This script demonstrates Groovy's static import support. See Groovy Users
* Guide at http://groovy.codehaus.org/Static+Import+Usage for additional
* details and examples.
*
* http://marxsoftware.blogspot.com
*/

// Specify Calender.getInstance() as "present" (Groovy feature)
import static Calendar.getInstance as current
println "Now is: ${current().format("yyyy-MM-dd HH:mm:ss.SSSZ")}"

// Specify another constant to represent Boolean.FALSE (Groovy feature)
import static Boolean.FALSE as UNTRUE
println "UNTRUE is ${UNTRUE}"

// Specify another name for color WHITE (Groovy feature)
import static java.awt.Color.WHITE as POLAR_BEAR_COLOR
println "Polar Bear's color is ${POLAR_BEAR_COLOR}"

// Invoke Math constants directly without need to scope them explicitly (J2SE 5 feature)
import static java.lang.Math.*
println "PI is ${PI} (${Math.PI})"
println "E is ${E}"

// Invoke Math operations directly without need to scope them explicitly
println "2^5 = ${pow(2,5)}"
println "Square root of 256 = ${sqrt(256)}"

The output from running the above script is shown in the next screen snapshot.


Conclusion

Not everyone is a fan of Java's static import, but I think they it does have its time and place (the documentation states to use it very sparingly). Groovy's feature-rich static imports can make for more readable scripts when used appropriately.