One of the APIs that Google makes available is a RESTful web service known as the Chart API, or, more formally, Google Chart Tools Image API.1 The documentation is located at https://developers.google.com/chart/image/. The chart tools provide a rich API for JavaScript users, but the inputs are ultimately URLs with query parameters.
A developer sends a request to the base URL https://chart.apis.google.com/chart and appends query parameters to specify the type of chart, its size, the data, and any
1 Google officially deprecated the image charts portion of Google Chart Tools on April 20, 2012. As of summer, 2013, the API still works. It is used here both as a nice, self-contained example and as a simple application that illustrates many Groovy features. Other examples of accessing publicly available services are given throughout the book.
labels. Because that API also needs a “Hello, World” example, here’s the URL for a three-dimensional pie chart:
https://chart.apis.google.com/chart?
cht=p3&
chs=250x100&
chd=t:60,40&
chl=Hello|World
This URL would be all on one line but is written out here (and in the documentation) for illustra- tion purposes. After the base URL, the parameters list the chart type (cht) as a 3D pie chart, the chart size (chs) as 250 by 100 pixels, the chart data (chd) as 60 and 40 in simple text format, and the chart labels (chl) “Hello” and “World.” Type that URL into a browser and the resulting image is returned, as shown in figure 2.1.
The URL shown is hard-wired to produce the chart in figure 2.1. To make this more general, I’ll show how to produce the URL from strings, lists, maps, closures, and builders.
GOAL Write a Groovy script to generate the “Hello, World” 3D pie chart as a desktop application.
In the process, I’ll discuss
■ String manipulation
■ Lists and maps
■ Processing data using closures
■ Groovy builder classes
In this case I’ll implement the steps in a simple script; later, it could be converted to a class for integration purposes.
2.2.1 Assembling the URL with query string
To start, I need a variable to represent the base URL. In a Groovy script you don’t actu- ally have to declare any types at all. If you declare a type the variable becomes local to the script. If not, it becomes part of the “binding,” which is discussed in the next chap- ter. Here, because I know the URL will be contained in a string before I convert it, I’ll declare the variable to be of type java.lang.String:
String base = 'http://chart.apis.google.com/chart?'
Groovy is optionally typed. This means you can specify a type if you want to, or you can use the keyword def if you don’t know or care. There’s some debate among develop- ers about when to use def and when to specify a type. Dierk Koenig, lead author on the superb Groovy in Action (Manning, 2007), says it this way:
Figure 2.1 The Google Chart API
“Hello, World” example
21 Accessing Google Chart Tools
USING DEF If you think of a type, type it (from Dierk Koenig). In other words, if you know a variable will be a String, or a Date, or an Employee, use that type of variable.
In my own experience, I used to use def a lot, but as time goes by I use it less and less.
I agree with Dierk, with the addition that when I’m tempted to use def I often pause a moment and try to think of an actual type before using it. Other developers have other styles, though. That’s the beauty of an optionally typed language: there’s room for everybody.
I now need to append the query parameters to this URL. Rather than write the query string directly I’m going to use a typical idiom for this type of application, which is to build a map and then generate the query string from the map parameters. With that in mind, here’s the map of parameters:
def params = [cht:'p3',chs:'250x100',
chd:'t:60,40',chl:'Hello|World']
In Groovy you create a map with square brackets, and each entry consists of keys and values separated by a colon. The keys are assumed to be strings by default. The val- ues can be anything. By default, the params variable is an instance of java.util .LinkedHashMap.
COLLECTIONS Groovy has native syntax for lists and maps. Map keys are assumed to be strings.
Each corresponding value is surrounded by single quotes. In Groovy, single-quoted strings are instances of java.lang.String. Double-quoted strings are “interpolated”
strings, known (unfortunately) as GStrings. I’ll show an example of string interpola- tion later in this program.
To transform the map into a query string I first need to convert each of the map entries into strings of the form “key=value,” and then I need to concatenate them all together using ampersands as separators.2 The first step is accomplished by using a special method added to all Groovy collections, known as collect. The collect method takes a closure as an argument, applies the closure to each element of the col- lection, and returns a new collection containing the results.
Closures are introduced in the next sidebar and discussed extensively throughout the book, but for the moment think of them as blocks of code representing the body of a function, which may take dummy arguments. In the case of collect, when applied to a map, the closure can take either one or two arguments. If the closure takes one argument, the argument represents a Map.Entry; with two arguments, the first is the key and the second is the value for each entry.
2 I also need to URL-encode the map entries, but in this case they’re already fine. In other examples of RESTful web services I’ll demonstrate the encoding process.
To transform the map into a list of key=value pairs, the following two-argument closure works in the collect method:
params.collect { k,v -> "$k=$v" }
In Groovy, if the last argument to any method is a closure you can put the closure out- side the parentheses. In this case the only argument to collect is a closure, so even the optional parentheses are omitted.
The result of the operation is shown here:
["cht=p3", "chs=250x100", "chd=t:60,40", "chl=Hello|World"]
This process is illustrated in figure 2.2.
To create the query string, use another method added by Groovy to collections, called join. The join method takes a single argument that’s used as the separator when assembling the elements into a string. To create a query string, invoke join with an ampersand as an argument:
["cht=p3", "chs=250x100", "chd=t:60,40", "chl=Hello|World"].join('&')
The result is the needed query string, as shown here:
"cht=p3&chs=250x100&chd=t:60,40&chl=Hello|World"
Here’s the entire process so far, taking the base URL and the parameter map, and building the Google Chart URL:
String base = 'http://chart.apis.google.com/chart?' def params = [cht:'p3',chs:'250x100',
chd:'t:60,40',chl:'Hello|World']
String qs = params.collect { k,v -> "$k=$v" }.join('&')
What is a closure?
A closure is a block of code, delimited by curly braces, which can be treated as an object. The arrow notation is used to specify dummy arguments. In the closure applied to the map in the current example, the two dummy arguments are k and v, which represent the key and value of each entry. The expression on the right side of the arrow says to substitute each key and value into a GString separated by an equals sign. This collect method takes each entry in the map and converts it into a string with the key assigned to the value, and produces a list of results.
collect { k,v -> "$k=$v" } [cht:'p3', chs:'250x100', chd:'t:60,40', chl:'Hello|World']
["cht=p3", "chs=250x100", "chd=t:60,40", "chl=Hello|World"]
Figure 2.2 Apply collect to a map to convert it into a list, where each entry is transformed into a string.
23 Accessing Google Chart Tools
The result of all this manipulation is actually a string, not a URL. Before convert- ing it to a URL, let me first verify that the process worked. Normally this would require a test, as discussed extensively in chapter 6 on testing. Here, however, I’ll just use the Groovy assert keyword, which takes a boolean expression as an argu- ment. If the expression is true, nothing is returned, but if not, you get the error printed to the console. In this case I’ll use the contains method from the Map interface to check that each of the entries from the params map appears in the query string in the proper format:
params.each { k,v ->
assert qs.contains("$k=$v") }
THE ASSERT KEYWORD Groovy asserts are an easy way to verify correctness. An assert returns nothing if the expression is true, and prints a detailed error message if it’s not.
One of the advantages of the join method is that you don’t have to worry about acci- dentally adding an ampersand at the beginning or end of the string. It only adds the separator internally.
Note also that this is a case where the parentheses (on the join method) are needed. In Groovy, if you leave off the parentheses when calling a method with no arguments the compiler assumes you are asking for the corresponding getter or setter method. Because I want the join() method (and not getJoin(), which doesn’t exist), I need the parentheses.
2.2.2 Transmitting the URL
The Groovy JDK adds the toURL() method to the String class. As you might imag- ine, this method converts an instance of java.lang.String into an instance of java.net.URL.
To send an HTTPGET request to a URL and retrieve the results, convert the string to a URL and invoke another Groovy JDK method, the getText() method, added to java.net.URL. In other words, the data on a web page can be retrieved from this code:
url.toURL().text
The Groovy JDK
Groovy adds many helpful methods to existing Java library classes. Many, many times I’ve found methods added to, say, String, Date, or Collection that I always wished were in Java all along. The set of methods added by Groovy is known as the Groovy JDK and has its own set of JavaDocs. The Groovy JDK documentation is available via a link from the Groovy home page.
The Groovy JDK is discussed in more detail in chapter 3.
Here I’m deliberately using the text property of the URL class, knowing that the effect will be to invoke the getText() method. There’s nothing wrong with actually calling getText, but this is more idiomatic Groovy.
Normally this would be exactly the code I want, and I use this technique in some of the examples in the chapters on web services, but in this particular case the result isn’t text. Google Chart takes the URL generated here and returns a binary image, so con- verting it to text isn’t very helpful.
GROOVY PROPERTIES Accessing properties in Groovy automatically invokes the associated getter or setter method.
Next I’ll build a Swing user interface that includes the image in a javax.swing .ImageIcon. This will give me a chance to illustrate a builder, which is a great illustra- tion of Groovy metaprogramming.
2.2.3 Creating a UI with SwingBuilder
In Groovy every class has a metaclass. A metaclass is another class that manages the actual invocation process. If you invoke a method on a class that doesn’t exist, the call is ultimately intercepted by a method in the metaclass called methodMissing. Likewise, accessing a property that doesn’t exist eventually calls propertyMissing in the metaclass. Customizing the behavior of methodMissing and propertyMissing is the heart of Groovy runtime metaprogramming.
Groovy metaprogramming is a large subject, but here I’ll demonstrate one of its helpful results: the creation of builder classes. In a builder, the call to methodMissing does something specific for that type of builder.
Here I’ll illustrate a Swing builder. This is a class that intercepts names of compo- nents and constructs a Swing user interface out of the results. This is actually easier to demonstrate than to explain. I’ll start, however, by adding some imports to the Google Chart script I’ve been constructing so far:3
import java.awt.BorderLayout as BL import javax.swing.WindowConstants as WC import groovy.swing.SwingBuilder import javax.swing.ImageIcon
3 That’s another one of the “Duh! Why didn’t we do that all along?” type of revelations that Java developers get all the time when they first learn Groovy. Why is it we only import java.lang in Java programs? Why not import lots of typical packages? Wouldn’t that make coding easier? Groovy says yes.
Automatic imports
You may have noticed that I haven’t yet needed any import statements at all. Java automatically imports the java.lang package. Groovy imports java.lang, as well as java.util, java.io, java.net, groovy.lang, groovy.util, java.math.Big- Integer, and java.math.BigDecimal.3
25 Accessing Google Chart Tools
In this script I’m importing three classes from the Java standard library. The first two imports use the as operator to build an alias for the respective classes. That way the code that uses BorderLayout and WindowConstants can just write BL or WC instead.
I’m also adding in the ImageIcon class, which will hold the image returned by Google Chart. The import from the Groovy library is SwingBuilder, which will be used to con- struct the Swing UI.
THE AS KEYWORD The as keyword has several uses, one of which is to provide an alias for imported classes. The as keyword corresponds to the asType method, which was added to java.lang.Object as part of the Groovy JDK.
In the case of SwingBuilder you invoke methods that don’t exist on the builder but that are translated to the corresponding Swing API. For example, by calling the frame method you’re actually instantiating the JFrame class. Giving it a map-like argument of visible:true corresponds to calling the setVisible method with a true argument.
Here’s the code that uses the builder. Each method not in SwingBuilder is trans- lated to the proper method call on the Swing library class:
SwingBuilder.edt {
frame(title:'Hello, World!', visible:true, pack: true, defaultCloseOperation:WC.EXIT_ON_CLOSE) {
label(icon:new ImageIcon("$base$qs".toURL()), constraints:BL.CENTER)
} }
The edt method on SwingBuilder builds a GUI using the event dispatch thread. It takes a closure as an argument, and this is where the fun starts. The first statement inside the closure is a call to the frame method, but the fact is, there’s no frame method in SwingBuilder. The builder’s metaclass intercepts that call (via method- Missing) and interprets it as a request to instantiate the javax.swing.JFrame class.
The frame method here lists a series of map entries, which are intended to supply val- ues for the title, visibility, and close operation on the JFrame. The builder interprets them as calls to setTitle, setVisible, and setDefaultCloseOperation on the JFrame instance.
After the parentheses there’s another closure. That’s interpreted to mean I’m about to supply components that will be added to the JFrame instance. The next call is to the label method, which of course doesn’t exist. The Swing builder knows to gen- erate a JLabel instance as a result, call its setIcon method with a new ImageIcon holding the image returned by Google Chart, and place the JLabel in the center of a BorderLayout.
Finally, after the frame closure I invoke the pack method on JFrame to make the resulting GUI just big enough to hold the image. The next listing contains the com- plete script (without the asserts, just to keep the listing short).
import java.awt.BorderLayout as BL import javax.swing.WindowConstants as WC import groovy.swing.SwingBuilder import javax.swing.ImageIcon
def base = 'http://chart.apis.google.com/chart?' def params = [cht:'p3',chs:'250x100',
chd:'t:60,40',chl:'Hello|World']
String qs = params.collect { k,v -> "$k=$v" }.join('&') SwingBuilder.edt {
frame(title:'Hello, Chart!', pack: true,
visible:true, defaultCloseOperation:WC.EXIT_ON_CLOSE) { label(icon:new ImageIcon("$base$qs".toURL()), constraints:BL.CENTER)
} }
The resulting image is shown in figure 2.3.
The next example demonstrates Groovy’s XML parsing and generation capabilities, database manipulation, regular expressions, groovlets, and more.