Wednesday, October 31, 2007

Searching Jars with Java Closures

(Ed's note: see this post for a better example of closures. This one is intended for direct contrast with a Groovy script: the resulting Java is awkward.)

The last post has a "Hello World", of sorts, for Neal Gafter's closure proposal.

In it, I mentioned that I have converted a Groovy jar searcher into pure Java, using Neal Gafter's prototype.

Here is the code. It is probably best to understand the previous post before looking into this. I may also be instructive to understand the Groovy code first.

The code is documented but here is a view from space:

  • The program takes a directory and a target string as arguments. It searches all jars in the directory for the target string, and reports the number of matches found.
  • A use-case is that you are looking for MyMissingClass in a bunch of jar files.
  • The program uses 2 closures as private members to the class.
  • One closure is myEntryChecker, which accepts a JarEntry and uses the target variable as a 'free lexical binding'. It returns a boolean if the name of the JarEntry contains the target string.
  • The other closure is myFileChecker, which accepts a File and uses the above closure as a 'free lexical binding'. Note that this one declares an IOException.
  • Note that there is a cool proposal for loop abstractions that is not yet implemented. This will make run() much more elegant. In fact, the invocation of closures seems to be a major motivation for the proposal.
(Btw, if you find the example less-than-elegant, blame me, not closures. The intent is not to introduce closures, per se, but instead to illustrate the new Java syntax by leveraging a simple-yet-useful program implemented in Groovy.)


import java.io.*;
import java.util.jar.*;
import java.util.Enumeration;

public class JarSearcher {

private String target;
private String fileName;

// This closure checks a JarEntry's name for a target string
// @param jarEntry
// @return boolean true if found
// @free target the string we're looking for
// ('free' is my way of denoting a free variable)

private { JarEntry => boolean } myEntryChecker = {
JarEntry jarEntry =>
// expression! no return statement or semi-colon
// Thanks to Christian Ullenboom for the simpler version
jarEntry.getName().contains(target)
};

// This closure iterates over the entries in a jar file and
// checks each one for a target string (via another closure)
// @param file
// @return int # of occurences
// @throws IOException
// @free myEntryChecker
// ('free' is my way of denoting a free variable)

private { File => int throws IOException } myFileChecker = {
File file =>
int count = 0;

fileName = file.getName();

if( fileName.indexOf(".jar") != -1 ) {

JarFile jarFile = new JarFile(file);

// old-style Enumeration
// don't blame me or closures!
for( Enumeration e = jarFile.entries() ;
e.hasMoreElements() ; ) {
JarEntry entry = (JarEntry) e.nextElement();
if( myEntryChecker.invoke(entry) ) {
count++;
}
}
}

// expression! no return statement
count
};

// This method lists the files in a directory and
// uses a closure to check each file for a target
// string (if it is a jar file)
// @param args[]
// @throws IOException (because the closure declares it)

public void run(String[] args) throws IOException {
String searchDirStr = args[0];
target = args[1];

File searchDir = new File(searchDirStr);

// NOTE: Neal has a proposal that would make this
// iteration much easier
for(File file : searchDir.listFiles() ) {
int count = myFileChecker.invoke(file);

if( count != 0 ) {
System.out.println(
"found " + count + " match(es) in " + fileName );
}
}
}

public static void main(String[] args) {
try {
JarSearcher jarSearcher = new JarSearcher();
jarSearcher.run(args);
} catch(Exception ex) {
// TODO
}
}
}

4 comments:

Christian Ullenboom said...

Nice example. Maybe you want to simplify fileName.indexOf(".jar") != -1 to fileName.contains(".jar").

Michael Easter said...

Christian, thanks for the comment...

That vastly simplifies the closure. It can now look like this

private { JarEntry => boolean } myEntryChecker = {
JarEntry jarEntry =>
jarEntry.getName().contains(target)

};

Neal Gafter said...

I'm flattered that you're trying the closures prototype, but this is not the best example of how they help organize code. I think both closures in this program would be better expressed as ordinary private methods.

Michael Easter said...

I agree, Neal, that this Java is not ideal if written from scratch.

My intent to was to contrast the syntax of an existing Groovy program, where the closures make more sense, with its File.eachFileRecurse() iteration.

IMO, the Groovy is easier for newbies to read, and then acts as a reference for the Java closure syntax.

Readers: this wasn't intended as a demonstration of closures per se. As in the original post, if this seems awkward, blame me, not closures.