This week I’m at UberConf (http://uberconf.com), a truly alpha-geek experience with sessions morning, afternoon, and evening. On Tuesday I did a day-long tutorial on Groovy for Java developers, which was really fun. I love teaching Groovy to existing Java developers. Sometimes they get tears in their eyes at how much easier life can be. 🙂 Wednesday I had a Spock workshop followed by a Grails workshop. Yesterday I did my Bayes’ Rule talk (“Bayes’ Rule Says You’ll Attend This Talk”), and today I’m doing my “Managing Your Manager” talk and my “Making Java Groovy” talk again.
Making Java Groovy has now moved into the production phase. So far this means discussing art work with the illustrators and trying to explain to them what I actually meant with my limited attempts at using a drawing tool.
I’ve been working on this book for nearly five years, and that means some of the earliest chapters needed some serious updating recently. When I started I think Groovy had just released version 1.6, so it’s changed versions at least four times during the writing process. Groovy 2.2 may even come out when the book shows up in print, at which point I’ll have to go back to the GitHub repo and update all the Gradle build files again.
One of the earliest chapters I wrote was the “Groovy by Example” chapter. In it I used the Google Chart API (which is now deprecated but still available) and an early version of my Groovy Baseball application. I really wanted to update all that to newer examples, but there simply wasn’t time, especially given that they all still worked correctly.
Still, I did take the time to write another example, even if it turned out to be too late to add it to the book. I’d like to show it here, because it’s simple but still pretty interesting, which is a good combination.
In many of my presentations (including one I’ll be doing later today), I like to access the ICNDB web site as an example of a RESTful web service that serves up JSON data. That’s ICNDB, as in the Internet Chuck Norris Database. ICNDB is a RESTful service in name only; it doesn’t do content negotiation (it doesn’t even set the “Content-Type” header correctly, a fact I discovered when I made an Android front end for it — see the ICNDB app in the Google Play store) and it only supports GET requests.
(Mandatory jokes: wouldn’t a RESTful service that only supports GET requests be called a GETful service? And if it’s stateless, wouldn’t that make it a FORGETful service? Insert rimshot here.)
The Groovy JDK makes it trivially easy to access a URL with a GET request. Groovy adds a toURL
method to the String
class, which converts a String
into an instance of java.net.URL
, and a getText
method to the URL
class, which returns the response as a String
. Calling it is as simple as:
[sourcecode language=”groovy”]
import groovy.json.JsonSlurper
String base = ‘http://api.icndb.com/jokes/random?limitTo=%5Bnerdy%5D’
def json = new JsonSlurper().parseText(base.toURL().text)
println json?.value?.joke
[/sourcecode]
Following the normal Groovy idiom, I invoke getText
by accessing the text
property of the URL. Then I use a JsonSlurper
to parse the result, which essentially converts it into a nested map-based data structure. The JSON object has a value
property that holds another JSON object, which stores the actual joke in a property called joke
. I don’t really need the safe de-reference operator here, but it doesn’t hurt.
The results are similar to:
Chuck Norris can unit test entire applications with a single assert.
The interesting question then becomes, how do I turn this into a client-side application with a graphical interface?
The Groovy API includes a so-called “builder” class called SwingBuilder
, which makes Swing development almost (but not quite) cool. It even has easy ways to handle threading issues, so that I can access the service off of the event dispatch thread, but update the GUI back on it. The key is in the doOutside
and doLater
methods. The former allows you to do work without locking up the GUI, while the latter is where you update the interface.
(The best discussion of SwingBuilder
I’ve ever seen is contained in Griffon in Action, by Andres Almiray, Danno Ferrin, and Jim Shingler.)
Here’s the whole script, which I’ll explain afterwards. In the book source code (just because I couldn’t put the app in the book doesn’t mean I couldn’t add it to the repo), this script is called icndb_with_label_and_button.groovy
.
[sourcecode language=”groovy”]
import groovy.json.JsonSlurper
import groovy.swing.SwingBuilder
import java.awt.BorderLayout as BL
import java.awt.Color
import java.awt.Font
import javax.swing.WindowConstants as WC
String startHTML = "<html><body style=’width: 100%’>"
String endHTML = ‘</body></html>’
String base = ‘http://api.icndb.com/jokes/random?limitTo=%5Bnerdy%5D’
def slurper = new JsonSlurper()
new SwingBuilder().edt {
frame(title:’ICNDB’, visible: true, pack: true,
defaultCloseOperation:WC.EXIT_ON_CLOSE) {
panel(layout:new BL(), preferredSize:[300, 250], background: Color.WHITE) {
label(‘Welcome to ICNDB’,
constraints:BL.NORTH,
font: new Font(‘Serif’, Font.PLAIN, 24),
id: ‘label’)
button(‘Get Joke’, constraints:BL.SOUTH,
actionPerformed: {
doOutside {
def json = slurper.parseText(base.toURL().text)
doLater {
label.text = "${startHTML}${json?.value?.joke}${endHTML}"
}
}
}
)
}
}
}
[/sourcecode]
A couple of the import
statements use the as
operator to create aliases. That is, I can use WC
in the script to refer to javax.swing.WindowConstants
, and BL
to represent java.awt.BorderLayout
. That’s an interesting but underused feature of the as
operator.
I want to add the joke to a JLabel
, which supports HTML. Therefore, I defined strings to surround the HTML when I put the joke in the label.
The SwingBuilder
class has an edt
method which lets me operate on the event dispatch thread when building the GUI. Then terms (actually, method calls) like frame
, panel
, label
, and button
create instances of JFrame
, JPanel
, JLabel
, and JButton
from the Swing API. The attributes, like visible:true
and title:'ICNDB'
invoke the setVisible
and setTitle
methods, respectively.
Arguably the best part, though, is the actionPerformed
property. If you’ve ever done Swing development, you know that clicking a JButton
generates an ActionEvent
. In order to capture the event you need an ActionListener
, which has a single method called actionPerformed
. In regular Swing development, the result is usually an inner class (or even an anonymous inner class) that implements the interface and updates the GUI. You use an inner class because the inner class is able to access the private attributes of the outer class, and when I used to do Swing development I normally made the labels and buttons attributes of my GUI class.
Here, though, I don’t need an inner class at all. Instead I assign the actionPerformed
property to a closure, and I’m finished. One of the nicest uses of closures is to eliminate anonymous inner classes this way.
Inside the closure I use the doOutside
method to access the service. Then I get back onto the EDT using doLater
and update the GUI. By giving the label an id
attribute, I was able to access it inside the doLater
closure using the value of the id.
That’s all there is to it. I’m not thrilled with the HTML parts, though, so I also implemented the script using a text area instead. The resulting script is in the file icndb_with_textarea_and_button.groovy
:
[sourcecode language=”groovy”]
import groovy.json.JsonSlurper
import groovy.swing.SwingBuilder
import java.awt.BorderLayout as BL
import java.awt.Color
import java.awt.Font
import javax.swing.WindowConstants as WC
String base = ‘http://api.icndb.com/jokes/random?limitTo=%5Bnerdy%5D’
def slurper = new JsonSlurper()
new SwingBuilder().edt {
frame(title:’ICNDB’, visible: true, pack: true,
defaultCloseOperation:WC.EXIT_ON_CLOSE) {
panel(layout:new BL(), preferredSize:[300, 250], background: Color.WHITE) {
scrollPane {
textArea(‘Welcome to ICNDB’,
constraints:BL.NORTH,
font: new Font(‘Serif’, Font.PLAIN, 24),
lineWrap: true, wrapStyleWord: true, editable: false,
id: ‘textArea’)
}
button(‘Get Joke’, constraints:BL.SOUTH,
actionPerformed: {
doOutside {
def json = slurper.parseText(base.toURL().text)
doLater {
textArea.text = json?.value?.joke
}
}
}
)
}
}
}
[/sourcecode]
I wrap the text area in a scroll pane, but other than that it’s essentially the same code. The result now looks like:
So that’s it: a small, but hopefully non-trivial application that demonstrates Groovy capabilities including builders, thread handling, RESTful web services, and even the as
operator. Have fun with it.
In the meantime, I’m ignoring the take down notice I received from Chuck Norris’s lawyers about my ICNDB app in the Play store. Sigh.
Leave a Reply