Sunday, July 8, 2007

Closures in Action: searching jars

A recent blog post talks about the problem of searching jars for class names. I have done something similar in Java, and ported the solution to Groovy.

The code is listed below. Some tidbits:

  • My uncommented Java program was 87 lines. The commented Groovy program is under 50.
  • The program is not fast but is straight-forward. It recursively searches a directory for jars, and searches each jar for a given string (e.g. "org/apache/log4j/Logger").
  • It uses 2 closures. This is a modest example of the power of generic algorithms (e.g. eachFile()) and closures.
  • If you aren't familiar with closures, Groovy is a fantastic way to "test drive" them before deciding on your stance about including them in Java.
// Usage:
// groovy Which [searchDir] [target]
//
// e.g. groovy Which c:\tomcat org/apache/log4j/Logger

import java.io.File;
import java.util.jar.JarFile;

// Closure: if jarEntry's name matches target, print fileName
// NOTE: fileName value comes from enclosing scope
myEntryChecker = {
jarEntry ->
int index = jarEntry.getName().indexOf(target);

if( index != -1 ) {
println "found match in " + fileName;
}
}

// Closure: if file is a jar, apply myEntryChecker
myFileChecker = {
file ->
if( file.isFile() && file.canRead() ) {
fileName = file.getName();

if( fileName.indexOf(".jar") != -1 ) {
JarFile jarFile = new JarFile(file);
jarFile.entries().each(myEntryChecker);
}
}
}

////////////////////////////////////////////////
// static void main(String args[])
//
// todo: sanity check arguments
def fileName
searchDir = args[0]
target = args[1]
println "\nsearching: " + searchDir
println "target: " + target + "\n"

new File(searchDir).eachFileRecurse(myFileChecker)

println "\ndone. "

10 comments:

Guillaume Laforge said...

Groovy is also pretty handy for some command-line scripting activities, and it's nice to see how one car reuse the JDK classes and methods easily, for instance when you introspected the entries in the Jar. Well done.
Guillaume

Jeff Brown said...

M,

This sort of stuff is a lot of fun to do in Groovy, isn't it? ;)

FYI...

Some none groovy-isms (all minor stuff):

You don't need to import java.io.File.

You don't need all the static typing.

Groovy property access looks like jarEntry.name and file.name instead of jarEntry.getName() and file.getName().

Well Done!

Michael Easter said...

Guillaume and Jeff, thanks for the props. Merci bien

re: non-Groovyisms. Excellent point, Jeff. I wondered how "Groovy-esque" it was, since it was morphed from Java.

If no one has coined it, I offer "syntax melting" for the phenomenon of a program gradually morphing from Java into pure Groovy.

I have written a fair bit of Groovy but not enough to be a native speaker yet.

paulk_asert said...

Re: none groovy-isms
another minor one:
contains() not indexOf()

Agustín Ramos said...

I like Groovy, really! (after a few years of java it's a relief). But I like to use the right tool (at hand) for each job:


function findClassInJars {
 ace searchPath=$1; pattern=$2;
for f in `find $searchPath -name '*.jar'`; do
if [[ "Zip" == `file -b $f | sed 's/\(^Zip\).*/\1/'` ]]; then
if [[ `unzip -l $f | grep -q "${pattern}.class"; echo $?` == "0" ]]; then
echo "Found matching classes in $f: "
unzip -l $f | grep "${pattern}.class"
fi
fi;
done
}

Michael Easter said...

I like shell scripts, really! ;-)

But when something hits a loop or an if statement, I have always reached for a scripting language: first Perl, then Python, and now Groovy.

For people like me, who write shell scripts once every 6 months, it is a tough sell to say that the bash (?) script provided is the right tool for the job. It would take me 15 minutes to truly understand it.

The upshot: sometimes, there is more than one "right" tool.

Bobby Bissett said...
This comment has been removed by the author.
Bobby Bissett said...

Because I don't get to create GUIs in my real job, I like to write tools that are really a bit bigger than they need to be. My solution to the jar-searching problem:

https://jarsearch.dev.java.net/

You can grab the runnable jar file under the Documents and files link. It's not exactly polished, though.

bigblueranter said...

ya know, i'm trying to dig this groovy stuff but here is the problem solved in shell. it's not recursive but it is so much shorter and it isn't a big deal to create the dir check and recurse.
for x in `ls dirname*.jar`;do
jar -xvf | grep classnametofind;
done
rather than do this over and over i might just create a big text file of the exploded results that includes the path name and just grep that. a reinvocation of this little script with an append redirect and i can build a pretty big lookup file of jar locations incrementally.

bigblueranter said...

oops - missed that somebody already noted this - aah well. back to learning groovy.