Groovy + Selenium + Smack = Cleverbot Answering Service

September 14, 2011 at 8:07 am (Groovy)

There was a post on Reddit the other day about using Python to get Cleverbot to respond to incoming IMs. It looked like an interesting hack, but it used Python, Pidgin and D-Bus, none of which I have on hand right now… so I made something similar using Groovy, Selenium and Smack.

You can have it wait for someone to IM you, or you can tell it to troll someone on startup; either way, it automatically saves the chat transcript to disk on shutdown. Here’s the code:

@Grapes([
	@Grab('org.igniterealtime.smack:smack:3.2.1'),
	@Grab('org.igniterealtime.smack:smackx:3.2.1'),
	@Grab('org.seleniumhq.selenium:selenium-firefox-driver:2.5.0'),
	@Grab('org.seleniumhq.selenium:selenium-support:2.5.0')])
import org.jivesoftware.smack.XMPPConnection
import org.jivesoftware.smack.ChatManagerListener
import org.jivesoftware.smack.MessageListener
import org.jivesoftware.smack.Chat
import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.support.ui.WebDriverWait
import org.openqa.selenium.By
import com.google.common.base.Predicate
import java.util.concurrent.TimeUnit
import groovy.ui.SystemOutputInterceptor

if(args.length < 3 || args.length > 4) {
	println 'Usage: groovy chat.groovy server username password [userToTroll]'
	return
}

def oldSendMessage = Chat.metaClass.getMetaMethod('sendMessage', [String] as Class[])
Chat.metaClass.sendMessage = { String s ->
	println "cleverbot/me: $s"
	oldSendMessage.invoke(delegate, s);
}

def transcript = ''
new SystemOutputInterceptor({ transcript += it; true; }).start()

def driver = new FirefoxDriver()
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS)
driver.get 'www.cleverbot.com/'

def conn = new XMPPConnection(args[0])
conn.connect()
conn.login(args[1], args[2])

def messageListener = { chat, message ->
	println "$message.from: $message.body"
	def response = say message.body, driver
	chat.sendMessage response
} as MessageListener

def chatListener = { chat, createdLocally ->
	chat.addMessageListener messageListener
} as ChatManagerListener

conn.chatManager.addChatListener chatListener

addShutdownHook {
	driver.quit()
	conn.disconnect()
	new File('transcript.txt').write transcript
}

if(args.length >= 4) {
	def chat = conn.chatManager.createChat(args[3], null)
	chat.sendMessage 'Hey!'
}

while(true) { Thread.sleep 1000 }

def say(input, driver) {

	def field = driver.findElement(By.id('stimulus'))
	field.sendKeys input
	field.submit()

	def response = null
	def condition = {
		def element = driver.findElement(By.id('typArea'))
		def finished = (response == element.text
			&& !['', '|'].contains(element.text))
		response = element.text
		return finished
	} as Predicate
	def wait = new WebDriverWait(driver, 30).until(condition)

	return response
}

Here’s the first conversation it had with my brother:

cleverbot/me: Hey!
my brother: sup?
cleverbot/me: What?
my brother: huh?
cleverbot/me: Never mind.

Never mind?!? Come on, Cleverbot! You can do better than that! Let’s try again.

cleverbot/me: Hey!
my brother: sup?
cleverbot/me: Sep.

At this point my brother called me on the phone to ask what I was smoking… I didn’t pick up. Third time’s the charm, right? I tried again, tweaking the initial message to try to draw him into a conversation.

cleverbot/me: did you just call?
my brother: yeah
cleverbot/me: Yeah yeah yeah.
[ed.: this isn't looking so good...]
my brother: was seeing why you’re acting all goofy
cleverbot/me: Did you know what a hijab is?
[ed.: wha? where did that come from?]
my brother: but seriously, when i get the number from the sheet, if there is a 0 at the beginning it’s not included
cleverbot/me: But you continue to lie. You are a computer. You do not get up in the morning, you don’t look in the mirror… You can’t feel… And I feel sorry for you.
[ed.: facepalm...]
my brother: and your sheet won’t bring back values, so i fixed it but have to use an “if” statement which won’t work with your macro
cleverbot/me: Okay.

Okay, indeed. Bah. My brother was asking serious questions about a script I wrote for him, and all Cleverbot could think to talk about was hijabs and chatbot philosophy.

I think I’ll answer my own IMs… for now.

Comments Off

Gradle: Keeping Libraries Up To Date

August 8, 2011 at 7:03 pm (Gradle, Groovy, Maven)

After a successful proof of concept earlier this year, we’ve started using Gradle (instead of Maven) for new projects at work. One of the first things you might notice about Gradle is that (a) it’s new enough that there isn’t a plugin for every random requirement out there, and (b) it’s flexible enough that the missing plugins aren’t usually a problem — you just implement the functionality yourself.

Here’s a quick task that will let you know if your dependencies need to be updated, a la mvn versions:display-dependency-updates. It’s actually a bit longer than most Gradle snippets, so I suspect there’s a way to express it more succinctly. The irony, of course, is that it relies on the Maven central repo to determine whether or not any of your dependencies are out of date.

// Find any 3rd party libraries which have released new versions
// to the central Maven repo since we last upgraded.
task checkLibVersions << {
  def checked = [:]
  allprojects {
    configurations.each { configuration ->
      configuration.allDependencies.each { dependency ->
        def version = dependency.version
        if(!version.contains('SNAPSHOT') && !checked[dependency]) {
          def group = dependency.group
          def path = group.replace('.', '/')
          def name = dependency.name
          def url = "repo1.maven.org/maven2/$path/$name/maven-metadata.xml"
          try {
            def metadata = new XmlSlurper().parseText(url.toURL().text)
            def versions = metadata.versioning.versions.version.collect { it.text() }
            versions.removeAll { it.toLowerCase().contains('alpha') }
            versions.removeAll { it.toLowerCase().contains('beta') }
            versions.removeAll { it.toLowerCase().contains('rc') }
            def newest = versions.max()
            if(version != newest) {
              println "$group:$name $version -> $newest"
            }
          } catch(FileNotFoundException e) {
            logger.debug "Unable to download $url: $e.message"
          } catch(org.xml.sax.SAXParseException e) {
            logger.debug "Unable to parse $url: $e.message"
          }
          checked[dependency] = true
        }
      }
    }
  }
}

2 Comments

Scriptom Kudos

September 18, 2008 at 12:33 am (Groovy)

I recently had the chance to revisit some Groovy code written last year which makes use of the Scriptom library, a Groovy/COM bridge which allows you to script ActiveX and COM components from Groovy. The code in question was fairly fragile in the worst of ways: it would crash roughly half the time… randomly.

Now, I’m not sure if the crashes were caused by Scriptom or by Jacob, the underlying Java/COM bridge, but after upgrading to the latest version of Scriptom (1.5.4b11, which also uses a newer version of Jacob) all of these crashes have disappeared.

The migration process took a couple of hours, but only because I didn’t read the Migrating from Previous Versions of Scriptom section on this page first; if I had done so, the migration would have been a matter of minutes.

Anyway, kudos to the Scriptom team (which apparently includes Marc Guillemot, a fellow HtmlUnit hacker) on an excellent release of this very useful library!

2 Comments

Moving Groovy Forward: G2One

October 22, 2007 at 8:44 pm (Groovy, Java)

Earlier this month Guillaume Laforge (Groovy project manager), Graeme Rocher (Grails project lead) and Alex Tkachman (former JetBrains COO) announced the creation of G2One, a Groovy/Grails consultancy. I’m a little late to the party, but better late than never: congratulations!

It’s particularly encouraging to see Alex’s name on the list — he must be anticipating very good things if he chose to leave JetBrains in order to pursue this new opportunity. It’s also good to see money being thrown at dynamic languages on the JVM (aside from Sun’s). I think it’s a good harbinger of things to come.

Comments Off

Groovy Interoperability Redux

July 13, 2007 at 11:52 am (Groovy, Java)

A couple of months ago I posted a short article regarding interoperability between Java and Groovy. In it, I discussed the “chicken and egg dependency problem,” wherein a circular dependency between Java code and Groovy code makes it difficult to get either portion compiled:

What, then, is the solution? Well, we would probably need to use a single compiler (or compiler framework) for both languages. Classes defined in Java syntax would need to be aware of classes defined in Groovy syntax, and vice versa, before any bytecode is serialized into .class files… Given that Sun GPL’ed the Java compiler not too long ago, it doesn’t seem too far-fetched to wonder if someone from Groovy, JRuby, Jython or Rhino might not start exploring the possibilities.

Well, it looks like the good folks at JetBrains have taken a stab at exactly this sort of integration, and the resultant joint Java/Groovy compiler will be included in the main Groovy distribution starting with version 1.1-beta-2. I have to say, I think this is a brilliant move on the part of JetBrains. I’m guessing that Alex Tkachman (the source of the patch) probably enjoyed tinkering on this enough that he worked on it in his spare time, and the goodwill generated by this move has got to be tremendous.

Political and economic concerns aside, the detailed technical description provided by Jochen Theodorou makes for interesting reading. My first impression is that this implementation is not the optimal solution — the short synopsis is that the Groovy compiler creates temporary bytecode stubs for the Groovy classes, feeds these stubs to the Java compiler so that it doesn’t barf all over the place, and then comes back and replaces the stubs with real bytecode.

It is, however, probably the best solution that can be achieved short of overcoming the final bastion of JVM monolinguism: javac itself. Because once Sun agrees to modify the Java compiler such that it becomes a framework for pluggable compiler components, multiple first-class languages on the JVM will have become a reality. You’ll be able to write your string utility methods in Ruby — and take advantage of it’s great regex support. You’ll be able to write your data export functions in Groovy — and take advantage of the great Scriptom library. You’ll also be able to continue to write code in Java — all without artificial abstractions getting in the way.

The point is that you’ll be able to make the decision on a case-by-case basis, rather than project-by-project. Further, your decision won’t be colored by the decisions others made while implementing the libraries you depend upon. It’s gonna be sweet!

Comments Off

Mavenizing OC4J Client Libraries With Groovy

April 13, 2007 at 9:46 pm (Groovy, Java)

The project I’m currently working on uses Spring on both the server side and the client side to wire everything together. We’ve been coding away for three or four months, doing all of our client/server integration testing with lightweight standalone RMI using Spring’s excellent remoting support. However, the time has come to shoehorn our stuff into the production application server: OC4J. This change involves adding a thin stateless session bean layer over our Spring-managed beans.

The largest unforeseen issue which we’ve encountered thus far has been that in order to perform JNDI lookups from the remote client, the OC4J client libraries need to be on the classpath. We’re using Maven 2 for build management, so normally this wouldn’t be an issue: just install the JAR into the local repository. Unfortunately, the OC4J client JAR declares a custom “Class-Path” entry in its manifest file, using relative paths. This constitutes a dependency declaration of sorts, but not one which Maven can work with.

The solution was to “translate” the manifest classpath dependency mechanism into Maven’s POM dependency mechanism. The following Groovy script targets a single JAR file (in this case the central OC4J client JAR), reads the classpath manifest entry, and adds each referenced JAR to the local Maven repository before finally adding the targeted JAR to the repository. Each JAR added to the repository is a copy of the original JAR, the only difference being that the “Class-Path” manifest attributes are removed from the copies. The script also generates a POM file for each JAR, assigning it a custom dependency configuration based on the removed “Class-Path” manifest attributes.

This was a quickie implementation, so there are a number of limitations: it uses the runtime environment, so it only works on Windows; the base path needs to be configured carefully, or the generated artifact IDs will contain some oddities; the dependency processing is not recursive, so multi-level dependency trees are not supported; etc. All of the limitations are easily fixable, I just don’t need the extra functionality spacer

My apologies for the formatting, which doesn’t quite fit the site template…

// Installs the target JAR (see "jar" variable below) into the Maven 2
// repository, as well as any manifest classpath dependencies. All manifest
// classpath dependencies are used to build appropriate Maven POMs.
//
// maven.apache.org/plugins/maven-install-plugin/index.html
//
// @author Daniel Gredler

import java.util.jar.*;
import java.util.zip.*;

String groupId = "com.oracle.oc4j.client"
String version = "10.1.3.2.0"

String home = "C:javaoc4j-client-10.1.3.2.0\"
File base = new File(home)
File dir = new File(base, "j2ee/home")
File jar = new File(dir, "oc4jclient.jar")
String[] paths = getClassPath(jar)

paths.each() {
	File f = new File(dir, it)
	if(f.exists()) installJar(f, groupId, version, dir, home)
}

installJar(jar, groupId, version, dir, home)

println "Finished!"

def getClassPath(file) {
	JarFile jar = new JarFile(file)
	if(!jar.manifest) return null
	String cp = jar.manifest.mainAttributes.getValue("Class-Path")
	if(!cp) return null
	return cp.split("s+")
}

def getArtifactId(file, home) {
	String artifactId = file.canonicalPath
	if(artifactId.startsWith(home)) artifactId = artifactId.substring(home.length())
	if(artifactId.endsWith(".jar")) artifactId = artifactId.substring(0, artifactId.length() - 4)
	artifactId = artifactId.replaceAll("\", "-")
	println "Artifact name mapping: ${file.canonicalPath} -> ${artifactId}"
	return artifactId
}

def createPom(groupId, artifactId, version, dependencies, dir, home) {
	File pom = File.createTempFile("temp", ".pom")
	FileWriter writer = new FileWriter(pom)
	writer.write("<project>\r\n")
	writer.write("t<modelVersion>4.0.0</modelVersion>rn")
	writer.write("t<groupId>${groupId}</groupId>rn")
	writer.write("t<artifactId>${artifactId}</artifactId>rn")
	writer.write("t<version>${version}</version>rn")
	if(dependencies) {
		writer.write("t<dependencies>rn")
		dependencies.each() {
			File jar = new File(dir, it)
			if(jar.exists()) {
				String aid = getArtifactId(jar, home)
				writer.write("tt<dependency>rn")
				writer.write("ttt<groupId>${groupId}</groupId>rn")
				writer.write("ttt<artifactId>${aid}</artifactId>rn")
				writer.write("ttt<version>${version}</version>rn")
				writer.write("tt</dependency>rn")
			}
			else {
				println "WARNING: Not including dependency ${jar.canonicalPath}, because the file does not exist."
			}
		}
		writer.write("t</dependencies>rn")
	}
	writer.write("</project>rn")
	writer.close()
	return pom
}

def makeCopyWithoutClassPathManifestEntry(file) {

	JarFile jar = new JarFile(file)
	Manifest manifest = (jar.manifest ? new Manifest(jar.manifest) : new Manifest())
	manifest.mainAttributes.remove(new Attributes.Name("Class-Path"))

	InputStream input = new BufferedInputStream(new FileInputStream(file))
	File copy = File.createTempFile(getNamePrefix(file), getNameSuffix(file))

	BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(copy));
	JarOutputStream output = new JarOutputStream(bo, manifest)

	int b
	byte[] buffer = new byte[4096]
	jar.entries().each() {
		if(!it.name.endsWith("MANIFEST.MF")) {
			output.putNextEntry(new ZipEntry(it.name))
			InputStream is = jar.getInputStream(it)
			while((b = is.read(buffer)) != -1) output.write(buffer, 0, b)
			is.close()
		}
	}

	input.close()
	output.close()
	println "Copied ${file.canonicalPath} -> ${copy.canonicalPath}"
	return copy
}

def getNamePrefix(file) {
	if (file.name.contains(".")) return file.name.substring(0, file.name.lastIndexOf("."))
	else return file.name
}

def getNameSuffix(file) {
	if (file.name.contains(".")) return file.name.substring(file.name.lastIndexOf("."))
	else return null
}

def installJar(file, groupId, version, dir, home) {
	File copy = makeCopyWithoutClassPathManifestEntry(file)
	String artifactId = getArtifactId(file, home)
	String path = copy.canonicalPath
	String[] dependencies = getClassPath(file)
	int dependencyCount = (dependencies ? dependencies.length : 0)
	File pom = createPom(groupId, artifactId, version, dependencies, dir, home)
	String pomPath = pom.absolutePath
	installJar(groupId, artifactId, version, path, pomPath, dependencyCount)
}

def installJar(groupId, artifactId, version, path, pomPath, dependencyCount) {
	String cmd = "cmd /c mvn install:install-file -DgroupId=${groupId} -DartifactId=${artifactId} -Dversion=${version} -Dpackaging=jar -Dfile=\"${path}\" -DpomFile=\"${pomPath}\" -DcreateChecksum=true"
	println "Installing ${artifactId} with ${dependencyCount} dependencies using command: ${cmd}"
	println ""
	Runtime.getRuntime().exec(cmd)
}

Comments Off

The Future of Groovy Interoperability?

March 25, 2007 at 8:17 pm (Groovy, Java)

Last week I experimented with Groovy, one of the more popular dynamically typed languages available on the JVM. Specifically, I was trying to mix and match Java and Groovy, starting with a pure Java codebase, and merging certain sections to Groovy where it made sense — beginning with the domain objects. In a nutshell, I found that while Groovy is pretty tubular, my expectations with regards to its interoperability with Java were somewhat optimistic.

After a little thought and some prodding from Marc and Guillaume, it became clear that from an interoperability perspective Groovy use can be categorized into four broad use case scenarios. From simplest to most complex, they are:

  • Pure Groovy.
  • Groovy invoking Java.
  • Java invoking Groovy.
  • Groovy invoking Java invoking Groovy invoking… a.k.a. circular dependency.

The first use case is obviously the most trivial. If Groovy code didn’t work, there would be no Groovy project. This is the baseline from which the other use cases are built. The second scenario is where things start to get interesting. The implications of successfully covering the second use case are huge: bytecode generation, JVM as a platform (HotSpot!), and access to third party libraries representing over 10 years of development. Just in case some of you have been hiding under a rock, I should note that this has been possible for a while spacer

Moving from the second scenario to the third is trivial, as far as Groovy is concerned. Unfortunately, build tools and project management packages such as Maven need to be coerced into compiling the Groovy code before the Java code. This is a challenge, at least for Maven. This inflexibility, though not Groovy’s fault, does pose a problem. It seems the standard way of dealing with this dilemma is to hide all of your Groovy code behind pure Java interfaces, and then move the Java code, the interfaces, and the Groovy code into three separate modules that can be built serially. Obviously, this amounts to erecting an artificial barrier between your Java codebase and your Groovy codebase. However, if the future does indeed hold a harmonious coexistence of multiple first-class languages on the JVM, as Groovy enthusiasts hope, then these issues will fade as Java developers become multilingual and the more popular build systems begin to cater to their needs.

Now we arrive at the fourth and final scenario: circular dependency. Looking back, this is the problem I somewhat naively assumed had already been solved by the Groovy team. The Java domain object which I converted to Groovy implemented an interface defined in Java, and the domain object itself was obviously used throughout the application, implemented in Java. Thus, I couldn’t compile the Java code before compiling the Groovy code because the domain object referenced by the Java code had not and could not yet be compiled. Furthermore, I wouldn’t have been able to fix the problem by compiling the Groovy code first, because the domain object referenced an interface implemented in Java. Dierk Koenig’s Groovy in Action calls this “the chicken and egg dependency problem:”

Groovy and Java both have no problem accessing, extending, or implementing compiled classes or interfaces from the other language. But at the source code level, neither compiler is really aware of the other language’s source files. If you want to work seamlessly between the two languages, the trick is to always compile dependent classes using the appropriate compiler prior to compiling a class that uses a dependent class.

This sounds simple, but in practice, there are many tricky scenarios, such as compiling a Java file that depends on a Groovy file that depends on a Java file. Before you know it, you can quickly end up with intricate dependencies crossing the boundaries of each language. In the best scenario, you may have the alternate back and forth between the two language compilers until all the relevant classes are compiled. A more likely scenario is that it will become difficult to determine which compiler to call when. The worst case scenario — and it’s not uncommon — occurs when you have circular dependencies. You will reach a deadlock where neither language will compile because it needs the other language to be compiled first.

The obvious solution is to punt: get rid of the circular dependency, revert to either use case two or use case three, and rant about “open sores” software for a couple of days to make yourself feel better. But let’s dig a little deeper and leave the ranting for next week… what would really need to be implemented in order to achieve the final interoperability scenario? JSR 223 (Scripting for the Java Platform) is all about implementing script engines in Java, which then host some target scripting language; not exactly what we’re looking for. JSR 292 (Supporting Dynamically Typed Languages on the Java Platform) is looking to add a new JVM instruction called invokedynamic, allowing method invocation verification to occur dynamically, at runtime. This new bytecode would make compilation of dynamically typed languages easier, but doesn’t solve our present conundrum.

What, then, is the solution? Well, we would probably need to use a single compiler (or compiler framework) for both languages. Classes defined in Java syntax would need to be aware of classes defined in Groovy syntax, and vice versa, before any bytecode is serialized into .class files. I tried to stay away from the compiler classes in college, but it seems fairly obvious that, assuming Eric isn’t lying, you would need a compiler framework providing:

  • Pluggable lexical and syntactical analysis (one per language).
  • Unified semantic analysis, code optimization and generation.

JSR 199 (Java Compiler API) looks interesting, but it doesn’t provide this sort of pluggable architecture. However, given that Sun GPL’ed the Java compiler not too long ago, it doesn’t seem too far-fetched to wonder if someone from Groovy, JRuby, Jython or Rhino might not start exploring the possibilities.

There’s also a second possibility that might require a little less tinkering. According to Tom Ball, a technology director at Sun who works on Java programming language tools, an ingenious hacker might make use of the JSR 199 compiler API to create a custom JavaFileManager implementation such that calls to getJavaFileForInput() requesting JavaFileObjects of type JavaFileObject.Kind.CLASS would forward the requests to the Groovy compiler. This custom JavaFileManager would behave similarly to the existing GroovyClassLoader, providing a convenient facade for the Groovy compiler, but could possibly be written in such a way that circular dependencies would be resolved correctly.

As Dierk puts it:

Until the Java compiler is aware of classes not yet compiled in other languages, you have to use intermediary interfaces or abstract classes in Java to make the interaction between Java and Groovy smoother during the compilation process. Let’s hope some day the Java compilers will provide hooks for interacting with foreign compilers of alternate languages for the JVM.

So. The code is all out there. Does anyone want to bet on how long it takes someone to stop hoping and start scratching their itch?

4 Comments

Giving Groovy a Chance

March 18, 2007 at 4:41 am (Groovy, Java)

So HLS recently gave Groovy a shot by trying it out with the brand-spanking-new Tapestry 5, and the results were relatively promising. Then Marc Guillemot, of HtmlUnit and Canoo WebTest fame, started trying to get me to buy Dierk’s recently-published Groovy In Action spacer I had considered using Groovy to reduce the LOC in the domain objects for one of my side projects, but I’m using JPA annotations as part of my persistence configuration. As of this writing, only the most bleeding-edge Groovy bits will support annotations, so I decided it wasn’t worth pursuing. However, I recently realized that this particular project is going to require integration with different legacy database systems, which means I’d probably be better off doing my mapping in hbm.xml files.

Now that it’s highly unlikely that I will be using JPA annotations to map my domain objects to the backing database, I decided that it was time to give Groovy a try, and see if I could get rid of all the boilerplate getters and setters in my domain objects. I chose one of the simpler domain objects for the trial. It contained a little over 150 LOC, comprising 7 properties, overridden equals() / hashCode() / toString(), and a couple of trivial convenience methods.

Initial setup consisted of installing the Groovy Eclipse Plugin and took about 1 minute. My initial experience was similar to Howard’s — everything just worked. I immediately halved the number of lines in the class, since I could get rid of all my getters and setters. I should mention that before moving this particular domain object over into Groovy, I wrote extensive unit tests for it. I just can’t get over the feeling that giving up all the compile-time checks that Java has to offer somehow puts me on shaky ground.

After making sure that my unit tests still passed, I tried to figure out if there was anywhere else I could cut down on the LOC. I settled on the equals() and hashCode() methods, and decided that surely there must be a way in Groovy to declaratively express the fields that should be used as a bean’s identity. Unfortunately, the only thing I was able to find was GROOVY-27, an open enhancement wish filed four years ago. Eventually I had a pass at writing a GroovyBean superclass with generic equals() and hashCode() methods relying on an abstract getIdentity() method, which returns a list of the values comprising the bean’s identity. I could have done this in Java, and it was 5 times slower than the older implementation (which was already relatively slow due to the use of EqualsBuilder and HashCodeBuilder), but it reduced my LOC to 35. Plus, the root of all evil is premature optimization, right? spacer

Up to this point everything had gone relatively smoothly, but unfortunately what lay ahead was frustration upon frustration. First, in implementing the equals() and hashCode() methods as described above, I naively decided to use Groovy’s cool closure support for looping. Bad decision. Not surprisingly, there’s a lot of magic going on under the covers to get closures to work, and I got ClassNotFoundExceptions, ClassCastExceptions and StackOverflowExceptions until I got rid of the closures. Apparently some of the magic involves these essential methods, and Groovy is not happy to be using magic at this basic level.

Second, I’m using Maven 2 to manage this project, and apparently most Groovy enthusiasts use Ant. The result? Second-class support for Maven. Supposedly there’s a Maven plugin out there somewhere that worked every third Tuesday in 2004, but the “standard” way of integrating Maven 2 and Groovy is via Groovy’s ant task. It was painful enough to see the 100 lines of Java code I had just annihilated resurrected as ghastly XML code in my pom.xml file, but unfortunately the Groovy compilation ends up occurring after the standard Java compilation. This would be OK if I were writing Groovy code that never got used by Java code, but unfortunately, domain objects are the bedrock of any application and are used everywhere else. I suppose I could bind the Groovy compilation to some other build lifecycle phase which occurs before the standard compile phase, or put all my Groovy files in a sub-module referenced by the main project, or any number of other hacks, but the point is that I don’t want to spend my time fighting Groovy so that I can be three times as productive writing my domain objects.

Finally, there is the issue of the Groovy Eclipse plugin itself. It works well out of the box, but there is some room for improvement. For example, why must it compile into the bin-groovy directory? Why can’t it just use the project’s default output folder? Also, while you can right-click on a project and choose “Add Groovy Nature”, there’s not an option to “Remove Groovy Nature”. I’m also slightly OCD when it comes to code formatting, and it’s nice to be able to auto-format my code. Allowing the IDE to remove those trailing tabs for me probably doubles my productivity. However, the Groovy editor can’t auto-format.

All in all, aside from the undocumented no-closures-in-hashCode-or-equals issue, I found Groovy to be a well-polished alternative to Java on the JVM. As I mentioned earlier, the LOC in my domain objects was cut in half, and a declarative identity feature for beans would be absolutely killer. H

gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.