Groovy/Swing/YQL/Google Maps Lightning Talk
Posted on 15. Mar, 2009 by todd in Java, groovy
Lightning talks are another very cool aspect to the Java Posse Roundup. The lightning talks we do are five minutes long and they cover a wide range of topics. All the lightning talks are posted on the Java Posse YouTube Chanel. The recording for this lightning talk (of which the rest of this post describes) is here.
If you would rather not hear me blather on describing this code, you can see a quick 20 second demo of the running application here.
I decided to write a little groovy app that displayed a few participants from the roundup and their origin airports on a Google map. I wanted to illustrate a few common Groovy idioms and illustrate how easy it is to create a pretty functional app in just a few lines of code.
The first thing of note in the script is a small class that represents an Attendee. Notice there is no constructor, no getters or setters. The only method in the class is the toString method. This method isn’t actually called in the script but I wanted to illustrate what a simple Groovy String (GString) looks like. The method has an implicit return (as the string is the last statement in the method). The String has references to the properties in the class. These properties are enclosed by ${}.
8 9 10 11 12 13 14 15 | class Attendee { String name String lastName String email String origin public String toString() {"Name:${name} Last name:${lastName} eMail:${email} origin:${origin}"} } |
The next few lines of code construct new instances of our Attendee class, one for each attendee. Normally these would come from some sort of datasource like a database or something but to keep this example simple, they are just hardcoded here. There are a few things of interest in this code. The first thing to note is that the attendees variable is dynamically typed. It is constructed using an ArrayList that is typed. This is really just to illustrate that Groovy does support Java generics. A typical declaration of a list in groovy would be def attendees = [] the [] are shorthand for new empty ArrayList().
The next interesting feature is the << construct. This is the equivalent of attendees.add().
The last thing to note in this example is the way we’re constructing the Attendee class. We have a comma separated list of values that are prefixed by property names. Notice that in our class definition we don’t have a constructor. This is one of the nice features of Groovy, named parameters.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | def attendees = new ArrayList<Attendee>() attendees << new Attendee(name: "Peter", lastName: "Pilgrim", email: "pp@example.com", origin: "IAD") attendees << new Attendee(name: "Joel", lastName: "Neely", email: "jn@example.com", origin: "MEM") attendees << new Attendee(name: "Dianne", lastName: "Marsh", email: "dm@example.com", origin: "DTW") attendees << new Attendee(name: "Carl", lastName: "Quinn", email: "cq@example.com", origin: "SJC") attendees << new Attendee(name: "Dick", lastName: "Wall", email: "dw@example.com", origin: "SJC") attendees << new Attendee(name: "Joe", lastName: "Nuxoll", email: "jn@example.com", origin: "SJC") attendees << new Attendee(name: "Bill", lastName: "Pugh", email: "pp@example.com", origin: "SJC") attendees << new Attendee(name: "Dave", lastName: "Briccetti", email: "db@example.com", origin: "SFO") attendees << new Attendee(name: "Todd", lastName: "Costella", email: "tc@example.com", origin: "YYC") attendees << new Attendee(name: "Ståle", lastName: "Undheim", email: "su@example.com", origin: "FRA") attendees << new Attendee(name: "Eirik", lastName: "Bjørsnøs", email: "eb@example.com", origin: "MUC") attendees << new Attendee(name: "Andrew", lastName: "Harmel-Law", email: "al@example.com", origin: "EDI") attendees << new Attendee(name: "Oliver", lastName: "Gierke", email: "og@example.com", origin: "ORD") attendees << new Attendee(name: "Joe", lastName: "Sondow", email: "js@example.com", origin: "LGA") attendees << new Attendee(name: "Fred", lastName: "Simon", email: "fs@example.com", origin: "ORD") attendees << new Attendee(name: "Daniel", lastName: "Watson", email: "dw@example.com", origin: "ATL") |
The next section is an implementation of a SwingBuilder. Builders are a pretty common idiom in Groovy. There are MarkupBuilders, AntBuilders and others including a SwingBuilder. The SwingBuilder abstracts a lot of the nasty complex requirements for building a rich user interface in Java. Swing is the desktop UI library. It’s very powerful but can also be very complex to use. The SwingBuilder provides a very elegant abstraction on what turns out to be a complex model. One of the great things about the SwingBuilder (and all Builders for that matter) is that in just reading the code, it’s quite apparent what the user interface is going to look like. We start with a Frame. Within the Frame we have a panel and within the panel we have a button and a label. We have a second panel that contains a table and thrid panel that contains the Google Map wiget. The equivalent swing code to implement this would be many more lines of code probably two or three times the amount of code I would guess. The other thing is that it would be harder to visualize the layout of the application with standard swing code. With the builder implementation, we can see how the components are nested within each other.
I’ll talk about the {…} code behind the Refresh Button and the table in a second. Before we leave this code I wanted to talk a bit about the Map component. The Google Map component is based on some fine work by Josh Marinacci that can be found at java.net JXMapViewer.The interesting thing with respect to this example is that any Swing component can be wrapped by a widget call in the builder. This provides a very nice mechanism for reusing existing swing classes within the builder pattern.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | def swing = SwingBuilder.build { frame(title: "Java Posse Roundup 2009", pack: true, show: true, defaultCloseOperation: EXIT_ON_CLOSE, id: "frame") { borderLayout() panel(constraints: NORTH) { boxLayout() button(text: "Refresh", actionPerformed: { doOutside {...} }) label(id:"status") } panel(constraints: CENTER) { borderLayout() table() {...} } panel(constraints: SOUTH) { borderLayout() scrollPane() { widget(new JXMapKit(), defaultProvider: JXMapKit.DefaultProviders.OpenStreetMaps, dataProviderCreditShown: true, id: "map") } } } } |
Aside from the structure of our application, the most interesting code in the builder can be found in the actionPerformed closure. This closure will be called when the Refresh button is clicked. There is quite a bit going on in a very few lines of code. The first thing of note is the doOutside method that takes a closure. Quite simply the code in the closure will get executed in a separate thread. How cool is that? Within the closure we iterate over the attendees collection. For each entry in the collection we make a call to the Yahoo Geo Service. Many thanks to my friend Ido Green for introducing me to YQL via his lightning talk. The XmlSlurper class is a handy means for parsing XML content. The query we’re composing takes the origin airport {it.origin} and returns an xml document that contains a bunch of geographical information for the airport. In our case, we’re only interested in the latitude and logitude. We pull those values out of the resulting document and then construct a WayPoint. The WayPoint object is present in the JXMapViewer package and is relly the only thing we need to construct a pin point on the map.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | button(text: "Refresh", actionPerformed: { doOutside { def waypoints = new HashSet<Waypoint>() attendees.each() { def url = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.places%20where%20text%3D%22${it.origin}%22&format=xml" status.text = "Looking up Airport: ${it.origin}" def doc = new XmlSlurper().parse(url) if (doc.results) { def latitude = doc.results.place[0].centroid.latitude.toString() def longitude = doc.results.place[0].centroid.longitude.toString() waypoints << new Waypoint(Double.valueOf(latitude), Double.valueOf(longitude)) } } WaypointPainter painter = new WaypointPainter(); painter.setWaypoints(waypoints); map.getMainMap().setOverlayPainter(painter); status.text ="" } }) |
The last thing of note is the table entry. The table has a tablemodel which is constructed using the list of attendees. The model itself is very simple and consists of a propertyColumn that has a constructor that takes the column title and the name of the property to display. If you’ve attempted to implement a tablemodel in java (even a read only one like this one), I’m sure you can appreciate the brevity and cleanliness of this implementation.
67 68 69 70 71 72 73 | table() { tableModel(list: attendees) { propertyColumn(header: 'Name', propertyName: 'name') propertyColumn(header: 'Last Name', propertyName: 'lastName') propertyColumn(header: 'EMail', propertyName: 'email') propertyColumn(header: 'Origin', propertyName: 'origin') } |
That’s it. 87 lines of Groovy goodness that renders a Google Map in a swing app, utilizes YQL to resolve geo location information for airports.
The source and external libraries that are required to run this application are available here. You’ll need Groovy installed. This archive has idea project files included. If you use eclipse or netbeans you’ll have to add the three external libraries to your project manually.
