Cascading Lists with Groovy's SwingBuilder

At lunch yesterday, one of my ex-colleagues was telling me that he couldn't quite get cascading lists to work when using Groovy's SwingBuilder.

So I knocked up an example for him:

cascade-list-swingbuilder

Mum told me that it is good to share my toys, so:

import groovy.swing.*
import javax.swing.*

def swing = new SwingBuilder()

def masterData = [en: ['Hello, World!', 'How are you?'], es: ['Hola Mundo!', "Como Esta?"], can: ['Foon Ying!', 'Lei Ho Ma?']]

def detailModel = new DefaultListModel()

swing.actions() {
  action(id: 'masterAction',
          name: 'masterAction',
          closure: {e->
            def l = e.source
            if (!e.valueIsAdjusting) {
              def i = l.selectedValue
              detailModel.clear()
              masterData[i].each {
                detailModel.addElement(it)
              }
            }
          })
}
swing.frame(title: 'My Frame', pack: true, visible: true,
                 defaultCloseOperation: javax.swing.WindowConstants.EXIT_ON_CLOSE) {
  panel() {
    tableLayout() {
      tr {
        td {
          scrollPane() {
            list(valueChanged: masterAction.closure, visibleRowCount: 3, listData: masterData.collect {k, v -> k })
          }
        }
        td {
          scrollPane() {
            list(visibleRowCount: 3, model: detailModel)
          }
        }
      }
    }
  }
}

It may not be optimal, but it works.

There's one or two little 'gotchas' here.

First: having to pass 'masterAction.closure' directly to the first list seems wrong…elsewhere when using SwingBuilder one would just have to use 'masterAction' so this feels a little 'off' to me.

Second: the code shown works fine when run under Groovy 1.6.4 via IntelliJ Maia EAP 10666. When run directly in 1.6.4′s GroovyConsole, it gives an error:

Exception thrown: Cannot cast object '[en, es, can]' with class 'java.util.ArrayList' to class 'java.util.Vector'
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '[en, es, can]' with class 'java.util.ArrayList' to class 'java.util.Vector'

The workaround is to use:

list(..., listData: masterData.collect {k, v -> k } as Object[])

This is probably more 'correct' according to the API, anyway.

Still, something feels 'off' here…why the different behaviors?

These are minor niggles, however.

It's still a clean and clear solution.

Gotta love that TableLayout, as well.

[edit]
There was something a bit off, but it should be sorted by now.

Tags: Griffon, Groovy, Programming

Another Groovy Micro-Benchmark

I have been maintaining grungy code, with lots of Java-esque stuff like this:

{
def value = 99
def s = 'string' + value

I have been 'Groovifying' the code into a (slightly) more readable version:

{
def value = 99
def s = "string$value"

(This micro-change makes more sense/value if you see the real code but that's under wraps, so you'll have to trust me on this…)

Anyway, I started wondering if I was really doing the right thing, performance-wise, so I knocked up the following sequence of tests:

def ts = System.currentTimeMillis()
for (i in 1..1000000) {
}
println ("Took: ${System.currentTimeMillis() - ts}ms.")
// Took: 70ms.

VisualVM lets me see how many strings are created. Not too many, as one would expect:

groovy-perf-1

Here's the 'before' case:

def ts = System.currentTimeMillis()
for (i in 1..1000000) {
  def x = 'test' + i
}
println ("Took: ${System.currentTimeMillis() - ts}ms.")
// Took: 292ms.

groovy-perf-before

And the 'after' case:

def ts = System.currentTimeMillis()
for (i in 1..1000000) {
  def x = "test$i"
}
println ("Took: ${System.currentTimeMillis() - ts}ms.")
// Took: 368ms.

groovy-perf-after

There's clearly a lot more going on here, so it's good that the run time is still "roughly in the same ballpark" as before.

Was I doing the right thing? Well, the system I am working on is really pretty small and this sort of micro-level concern constitutes about 0.000000000001% of the performance of the system. In terms of readability, I think I am winning, so I'm still happy.

For completeness, there's a few other cases I hacked around on. None was particularly surprising:

def ts = System.currentTimeMillis()
for (i in 1..1000000) {
  def x = "test$i".toString()
}
println ("Took: ${System.currentTimeMillis() - ts}ms.")
// Took: 478ms.

This one is interesting because its pretty much a standard Java idiom:

def ts = System.currentTimeMillis()
for (i in 1..1000000) {
  def x = String.format('test%d', i)
}
println ("Took: ${System.currentTimeMillis() - ts}ms.")
// Took: 2583ms.

It's also the slowest (and VisualVM showed a lot of pattern matching going on "behind the scenes"). Its probably worth remembering this when anyone talks about how dynamic languages are not performant in "The Real World."

def ts = System.currentTimeMillis()
for (i in 1..1000000) {
  def sb = new StringBuilder('test')
  sb.append(i)
  def x = sb.toString()
}
println ("Took: ${System.currentTimeMillis() - ts}ms.")
// Took: 260ms.
def ts = System.currentTimeMillis()
for (i in 1..1000000) {
  def sb = new StringBuilder('test')
  sb < < i
  def x = sb.toString()
}
println ("Took: ${System.currentTimeMillis() - ts}ms.")
// Took: 300ms.

As is my wont, I'm not going to draw any conclusions (there's "lies, damn lies and performance statistics", remember…or maybe "the beauty of performance statistics lies in the eye of the beholder").

Hope you found all this interesting, however.

Curiosity may have killed the cat…it'll probably give me RSI.

Tags: Groovy, Programming

Excellent Presentation From Agile2009: "Mapping The Change Battlefield"

By Giora Morein: http://www.bigvisible.com/gmorein/agile2009-battlemapping/.

With exercises, too!

Wish I'd been there…

Tags: Agile

Groovy Map Surprises

I lost a fair bit of time today wrestling with the subtleties of Groovy Maps.

Take a look:

def val = 99
def map = [:]

map = ["fred$val":val]
println map["fred$val"]
// => null

map = [("fred$val"):val]
println map["fred$val"]
// => null

map = [("fred$val".intern()):val]
println map["fred$val"]
// => 99

// compilation error...ok, expected
//map = ['fred' + val:val]
//println map["fred$val"]

map = [('fred' + val):val]
println map["fred$val"]
// => 99

map = [("fred$val".toString()):val]
println map["fred$val"]
// => 99

def s = "fred$val"
map = [(s):val]
println map["fred$val"]
// => null

s = "fred$val"
map = [(s):val]
println map[s]
// => null

map = [:]
map.put("fred$val", 99)
println map.get("fred$val")
// => 99

The amount of strangeness shown here is all too much for my liking…

On leafing through the Groovy 'bible', GINA, I found the possibility of a hint of a reason:

There is also a corner case with GStrings if their values write themselves lazily.

Which led me to the Groovy site on Strings and GStrings, which gives the salient advice:

GStringsAreNotStrings

So, now I know.

It's partly (mostly?) my misconception…or foolishness: expecting a String to be a GString.

Well:

How can I possibly put a new idea into your heads, if I do not first remove your delusions?
"Doctor Pinero" in Life-Line (1939)

I now carrying one less delusion around with me. This is good.

Not sure I am any happier, TANJ it!

Here's a debugging nightmare:

final val = 99

println """
GString:"""
def map = [:]
// NB: without toString()
map.put("fred$val", '?')
println map.get("fred$val")
println map.get("fred99")
println map.get("fred99".toString())
println map.get('fred99')
println map["fred99"]
println map['fred99']
println map[("fred99")]
println map[('fred99')]
println map['fred' + 99]
println map["fred" + 99]
println map[("fred" + 99)]
println map[("fred" + 99).toString()]
map.each {k,v ->
  println "$k=>$v"
  }

println """
String:"""
map = [:]
// NB: with toString()
map.put("fred$val".toString(), '!')
println map.get("fred$val")
println map.get("fred99")
println map.get("fred99".toString())
println map.get('fred99')
println map["fred99"]
println map['fred99']
println map[("fred99")]
println map[('fred99')]
println map['fred' + 99]
println map["fred" + 99]
println map[("fred" + 99)]
println map[("fred" + 99).toString()]
map.each {k,v ->
  println "$k=>$v"
  }

println """
Buyer beware:"""
map = [:]
map.put("fred$val", val)
map.put("fred$val".toString(), val)
map.each {k,v ->
  println "$k=>$v"
  }

Here's what you get:

GString:
?
null
null
null
null
null
null
null
null
null
null
null
fred99=>?

String:
null
!
!
!
!
!
!
!
!
!
!
!
fred99=>!

Buyer beware:
fred99=>99
fred99=>99
Result: [fred99:99, "fred99":99]

TANSTAAFL, I guess…you win some, you lose some and this is but a small entry on Groovy's otherwise clean "rap sheet."

[edit]

Jim Shingler (who writes at: Shingler's Thoughts) sent me the following alternative:

def val = 99
def map = [:]

map."fred$val" = val
println map
println map["fred$val"]
println map."fred$val"

Seems to be a little more robust, thanks.

The takeaway message seems (to me) to be: "Don't use GStrings as keys in a map. Especially don't use GStrings with embedded placeholders because their lazy evaluation may make life awkward."

Tags: Groovy, Programming

Apologies To Groovyblogs.org Readers

As you may see, I have moved to WordPress and a new subdomain.

Because of this, I have had to re-publish my feed and those of you coming from GroovyBlogs.org may see a few duplicated submissions. I apologise for this…it's not a deliberate attention-grabbing ploy.

Normal service should be quickly resumed ;-)

GrIMMiS Article In September's GroovyMag

Another GroovyMag edition containing musings by yours truly has hit the streets.

The article is entitled "GrIMMiS-the Groovy Internet Mood Meter in SVG" and is a whimiscal look at using Scalable Vector Graphics with Grails and AJAX.

GrIMMiS

But wait! There's more!

I have deployed GrIMMiS as a Stax Cloud Application (on the Amazon Elastic Compute Cloud), so you can play with it (it's not particularly impressive, though…it only does one thing…). Here you go:

How is the Internet feeling today? How are you feeling today?

You'll need a browser that has good SVG support. I use Opera 10 and Firefox 3.5.
(I believe that the version of prototype currently shipped with Grails doesn't like Safari and Chrome…so aficionados of those browsers are currently out of luck.)

I'm not particularly a Microsoft basher, but when it comes to SVG, Microsoft should be thoroughly ashamed of Internet Explorer.

Tags: Grails, GroovyMag, Programming

Another Factoid For The Memory Box: Embedded Images In HTML

This is (yet) another thing I always forget how to do…

<img alt="Red Square" src="data:image/png,%89PNG%0D%0A [...elided...] %60%82" />

With a compatible brower, this gives:

Red Square

Doesn't necessarily work with all browsers (so you know what to do if you can't see a reddish-brown square), but it can come in handy at times.

As always, Wikipedia has the lowdown on the whole "Data URI" thing

Tags: Tools

More "No Clean Feed" Stuff

With the help of GetUp, I have faxed this email to my local MP: The Hon. Archibald Bevis MP Member for Brisbane [ALP].

Dear sir,

As an Australian with 25 year's worth of experience in the ICT industry I wish to register my opposition to the current proposals for a 'clean' internet.

I believe that this proposal:

  • is fundamentally undemocratic and may give governments present and future the ability to block information without scrutiny or accountability.
  • has the potential to significantly harm Australia's ICT industry. At the very least, it has already caused harm to Australia's international reputation (refer to conversations like http://tech.slashdot.org/article.pl?sid=09/02/12/048203 to see this).
  • is technically EXTREMELY weak. ALL other places that have tried this sort of thing have failed. Circumvention WILL be trivial and commonplace.
  • is NOT the only possible solution. There are plenty of alternatives - like distributing software to parents to use at home, or making the filter opt-in rather than mandatory. I favour the option of providing a 100% tax rebate for anyone who purchases their choice of commercially available internet filter from the existing market place. I have written about this in 'unacceptable.'

I STRONGLY urge you to prevent this ill-considered and insidious idea from becoming law.

I urge any Australian reader to please join with me in working to prevent this abomination of an idea from becoming law.

Tags: Rant

HTML Color Names

I always forget these, so I'll store the link here for safe keeping.

Tags: Tools

IBM Images A Single Molecule

My First Degree was in Applied Chemistry (from the University of Hertfordshire [nee Hatfield Polytechnic]) and this simply blows me away:

dn17699-1_300

It is an image of a single Pentacene molecule.

The possibilities are endless!