Groovy - Scripting for Java

Groovy - Scripting for Java

By Mark Volkmann, OCI Partner

February 2004


Introduction

Groovy is an open-source scripting language that is implemented in Java and is tightly integrated with it. It requires Java's JDK 1.4. Groovy adds some features of the Ruby and Python scripting languages to Java. Features of Groovy include dynamic typing, closures, easy object navigation and more compact syntax for working with Lists and Maps. These features and more are described in detail in this article.

Here's a quote from the Groovy web site. "Groovy is designed to help you get things done on the Java platform in a quicker, more concise and fun way - bringing the power of Python and Ruby inside the Java Platform."

Groovy scripts can use any Java classes. They can be compiled to Java bytecode (in .class files) that can be invoked from normal Java classes. The Groovy compiler, groovyc, compiles both Groovy scripts and Java source files, however some Java syntax (such as nested classes) is not supported yet.

In theory, entire applications could be written in Groovy that would have similar performance characteristics to an equivalent Java application. This distinguishes Groovy from other scripting languages such as Ruby, Python, Perl and BeanShell. One of the things that makes Groovy perform more slowly than Java today is that the generated bytecode uses reflection to call constructors and private/protected methods. This will be addressed over upcoming releases.

Groovy was created by James Strachan and Bob McWhirter. James is also involved in the development of many other open source products including Jelly, dom4j, Jaxen, Betwixt and Maven. Bob is the creator of Jaxen and Drools (an open source, object-oriented, Java rules engine).

This article doesn't cover every feature of Groovy, but it covers a large number of them. It assumes that you are familiar enough with Java syntax to be able to compare Java syntax to Groovy syntax.

If we could all agree on what makes a good programming language syntax then we wouldn't need so many of them. Based on the number of programming languages out there, we obviously don't agree. After reading this article, you may decide that you like Java syntax just fine and that Groovy syntax is just too much syntactic sugar for your tastes. If that is your conclusion, I encourage you to investigate BeanShell from Pat Niemeyer at http://www.beanshell.org. It sticks much closer to standard Java syntax. On the other hand, if you prefer the shorter syntax of Groovy then that's just groovy!

Downloading and Installing Groovy

To download Groovy, follow these steps.

  1. Visit http://groovy.codehaus.org/.
  2. Click the "Download" link in the top navigation bar.
  3. Click the "this site" link.
  4. Click a release to download.

To install Groovy, follow these steps.

  1. Unzip the downloaded file.
  2. Set the GROOVY_HOME environment variable to directory that was unzipped.
  3. Add to the PATH environment variable either $GROOVY_HOME/bin (UNIX) or %GROOVY_HOME%\bin (Windows).

Running Groovy

There are four ways to execute Groovy scripts. In all of these cases, the script lines are parsed, converted to Java source and compiled to Java bytecode.

Interactive Shell

The command groovysh starts an interactive shell where Groovy statements can be entered. Enter any number of statements, pressing the enter key after each. Statements are not evaluated or executed until the execute command is entered.

Interactive Swing Console

The command groovyConsole launches a Swing window. Enter Groovy statements in the bottom part of the window. Select Run from the Actions menu to execute them. Output appears in the top part of the window. Scripts can be opened and saved using the File menu.

GroovyConsole

Script File Execution

A Groovy script file (typically named with a .groovy file extension) can be executed using the command groovy script-name.groovy.

Compiled Script Execution

A Groovy script file can be compiled to a Java .class file using the command groovyc script-name.groovy. If there are loose statements in the script, this .class file will contain a main method, so it can be executed as a Java application using the command java script-name. The contents of the main method will be discussed later. The classpath must contain the groovy*.jar and asm*.jar files from the Groovy lib directory.

There’s even a custom Ant task to do this! The class is org.codehaus.groovy.ant.Groovyc.

Some Syntax Details

Here are some of the key differences between Java and Groovy syntax. Others will be covered later.

Dynamic Typing

Types are optional for variables, properties, method/closure parameters and method return types. They take on the type of whatever was last assigned to them. Different types can be used later. Any type can be used, even primitives (through autoboxing). Many type coersions occur automatically when needed such as conversions between these types: String, primitive types (like int) and type wrapper classes (like Integer). This allows primitives to be added to collections.

Added Methods

Groovy adds many methods to standard Java classes such as java.lang.Object and java.lang.String. See http://groovy.codehaus.org/groovy-jdk.html. Those added to Object are covered here. Others will be discussed later.

dump

This returns a string of the form

 <class-name@hashcode property-name=property-value ...>

For example,

 <Car@ef5502 make=Toyota model=Camry>.

print and println

These static methods print the toString value of object. For example, print car or println car.

invokeMethod

This provides dynamic method invocation using reflection. The syntax is object.invokeMethod(method-name, argument-array). The following example prints the value 4.

  1. s = 'abcabc' // a java.lang.String
  2. method = 'indexOf'
  3. args = ['b', 2]
  4. println s.invokeMethod(method, args)

Groovy Strings

Literal strings can be surrounded with single or double quotes. When double quotes are used, they can contain embedded values. The syntax for an embedded value is ${expression} which is just like Ruby except $ is used instead of #. Strings surrounded by double quotes that contain at least one embedded value are represented by a groovy.lang.GString object. Other strings are represented by a java.lang.String. GStrings are automatically coerced to java.lang.String when needed.

Javadoc for Groovy classes like groovy.lang.GString can be found at http://groovy.codehaus.org/gapi/.

Embedded values are very useful for implementing toString methods. For example,

 String toString() { "${name} is ${age} years old." }

Multi-line strings can be created in three ways. The following examples are equivalent. The last example uses what is called a "here-doc". After the three less-than characters, a delimiter string is specified. The string value includes all the characters between the first and second occurrence of the delimiter string. "EOS" (standing for "End Of String") is a common delimiter value, but others can be used.

  1. s = " This string
  2. spans three \"lines\"
  3. and contains two newlines."
  4.  
  5. s = """ This string
  6. spans three "lines"
  7. and contains two newlines."""
  8.  
  9. s = <<<EOS
  10. This string
  11. spans three "lines"
  12. and contains two newlines.
  13. EOS

Note that the last newline character in the previous code snippet is NOT retained.

The following methods are added to java.lang.String.

contains
This determines whether a string contains a substring. 'Groovy'.contains('oo') returns true.
count
This counts occurrences of a substring within a string. 'Groovy Tool'.count('oo') returns 2.
tokenize
This tokenizes a string using a given delimeter and returns a list of the tokens. Specifying the delimiter parameter is optional. It defaults to whitespace characters. 'apple^banana^grape'.tokenize('^') returns ['apple', 'banana', 'grape'].
minus
This removes first occurrence of a substring from a string. 'Groovy Tool' - 'oo' returns 'Grvy Tool'.
multiply
This repeats a string a given number of times. 'Groovy'* 3 returns 'GroovyGroovyGroovy'.

Regular Expressions (regex)

First, let's review the support for regular expressions in J2SE 1.4. Then we'll discuss how Groovy builds on this.

In J2SE 1.4, regular expressions are supported by classes in java.util.regex package. Pattern objects represent a compiled regex. They are created with Pattern.compile("pattern"). The javadoc for this class describes regular expression syntax. Matcher objects hold the results of matching a Pattern against a String. They are created with pattern.matcher("text"). To simply determine if the text matches the pattern, matcher.matches() is used. Backslashes in patterns must be escaped with an extra backslash.

Groovy supports regular expressions with three operators.

See the javadoc of Pattern and Matcher for additional methods.

For example,

  1. pattern = "\\d{5}" // matches zip codes (5 digits)
  2. text = "63304" // a zip code
  3.  
  4. println text ==~ pattern // prints "true"
  5.  
  6. m = text =~ pattern
  7. println m.matches() // prints "true"
  8.  
  9. // The next line requires a literal string for the pattern.
  10. // A variable can't used.
  11. p = ~"\\d{5}"
  12. m = p.matcher(text)
  13. println m.matches() // prints "true"

Groovy Scripts

Groovy scripts are source files that typically have a ".groovy" file extension. They can contain (in any order) loose statements, method definitions not associated with a class, and class definitions.

For example,

  1. // These are loose statements.
  2. println 'loose statement'
  3. myMethod 'Mark', 19
  4. println new MyClass(a1:'Running', a2:26.2)
  5.  
  6. // This is a method definition that
  7. // is not associated with a class.
  8. def myMethod(p1, p2) {
  9. println "myMethod: p1=${p1}, p2=${p2}"
  10. }
  11.  
  12. // This is a definition of a class that
  13. // has two properties and one method.
  14. class MyClass {
  15. a1; a2
  16. String toString() { "MyClass: a1=${a1}, a2=${a2}" }
  17. }

Method and class definitions do not have to appear before their first use. Loose methods get compiled to static methods in the class that corresponds to the script file. For example, a loose method named foo in a script called Bar.groovy will get compiled to a static method named foo in the class Bar. Loose statements are collected in a run method that is invoked by a generated main method

When groovyc is used to compile a script, the generated class will have a run method that contains all the loose statements and a main method that invokes run.

Currently scripts cannot invoke code in other scripts unless they are compiled and imported. This should be fixed soon.

Operator Overloading

Groovy supports operator overloading for a fixed set of operators. Each operator is mapped to a particular method name. Implementing these methods in your classes allows the corresponding operators to be used with objects from those classes. The methods can be overloaded to work with various parameter types.

Comparison Operators

a == b maps to a.equals(b) 
a != b maps to !a.equals(b) 
a === b maps to a == b in Java
a b maps to a.compareTo(b) 
a > b maps to a.compareTo(b) > 0 
a >= b maps to a.compareTo(b) >= 0 
a < b maps to a.compareTo(b) < 0 
a  maps to a.compareTo(b)

The comparison operators handle null values and never generate a NullPointerException. Null is treated as less than everything else.

Notice that in Groovy the == operator is used to test whether two objects have the same value and the === operator is used to test whether they are the same object in memory.

The compareTo method returns an int less than 0 if a < b, greater than 0 if a > b, and 0 if a is equal to b.

Other Operators

a + b maps to a.plus(b) 
a - b maps to a.minus(b) 
a * b maps to a.multiply(b) 
a / b maps to a.divide(b) 
a++ and ++a maps to a.increment(b) 
a-- and --a maps to a.decrement(b) 
a[b] maps to a.get(b) 
a[b] = c maps to a.put(b, c)

Groovy Closures

A closure is a snippet of code that optionally accepts parameters. Each closure is compiled into a new class that extends groovy.lang.Closure. This class has a call method that can be used to invoke a closure and pass parameters to it. They can access and modify variables that are in scope when the closure is created. Variables created inside a closure are available in the scope where the closure is invoked. Closures can be held in variables and passed as parameters to methods. This is particularly useful in certain list, map and string methods that are described later.

The syntax for defining a closure is

{ comma-separated-parameter-list | statements }

For example,

  1. closure = { bill, tipPercentage | bill * tipPercentage / 100 }
  2. tip = closure.call(25.19, 15)
  3. tip = closure(25.19, 15) // equivalent to previous line

Passing the wrong number of parameters results in an IncorrectClosureArgumentException.

The keyword it is used in closures with one parameter. The parameter list can be omitted and referred to in statements with it. For example, the following closures are equivalent.

  1. { x | println x }
  2. { println it }

Here's an example of a method that takes a List and a Closure as parameters. It is written as a "loose method", but it also could be written as a method of some class. The method iterates over the List, invoking the Closure on each item in the List. It populates a new List with the items for which the closure returns true and then returns the new List. Note that Groovy provides find and findAll methods that can be used instead of this.

  1. def List myFind(List list, Closure closure) {
  2. List newList = []
  3. for (team in list) {
  4. if (closure.call team) newList.add team
  5. }
  6. newList
  7. }

Here's an example of using this method.

  1. class Team { name; wins; losses }
  2.  
  3. teams = []
  4. teams.add new Team(name:'Rams', wins:12 , losses:4)
  5. teams.add new Team(name:'Raiders', wins:4 , losses:12)
  6. teams.add new Team(name:'Packers', wins:10 , losses:6)
  7. teams.add new Team(name:'49ers', wins:7 , losses:9)
  8.  
  9. winningTeams = myFind(teams) { it.wins > it.losses }
  10. winningTeams.each { println it.name }

There’s no need to write a method like myFind since the List class already has a findAll method. To use it,

winningTeams = teams.findAll { it.wins > it.losses }

Groovy Beans

Here's an example of a simple Groovy Bean.

  1. class Car {
  2. String make
  3. String model
  4. }

This class declares two properties and no methods. However, there is a lot going on behind the scenes. Classes, properties and methods are public by default. Public and protected properties result in private fields for which public/protected get and set methods are automatically generated. These can be overridden to provide custom behavior. For properties that are explicitly declared to be private, get and set methods are not generated.

The above Groovy code is equivalent to the following Java code.

  1. public class Car {
  2. private String make;
  3. private String model;
  4.  
  5. public String getMake() {
  6. return make;
  7. }
  8.  
  9. public String getModel() {
  10. return model;
  11. }
  12.  
  13. public void setMake(String make) {
  14. this.make = make;
  15. }
  16.  
  17. public void setModel(String model) {
  18. this.model = model;
  19. }
  20. }

The classes generated by Groovy Beans extend java.lang.Object and implement groovy.lang.GroovyObject. This adds methods the methods getProperty, setProperty, getMetaClass, setMetaClass and invokeMethod. groovy.lang.MetaClass allows methods to be added at runtime.

Groovy Beans can be created using named parameters. For example, the following code calls the Car no-arg constructor and then a set method for each specified property.

myCar = new Car(make:'Toyota', model:'Camry')

Groovy Lists

Groovy lists are instances of java.util.ArrayList. They can be created using a comma-separted list of values in square brackets. For example,

  1. cars = [new Car(make:'Honda', model:'Odyssey'),
  2. new Car(make:'Toyota', model:'Camry')]
  3.  
  4. println cars[1] // refers to Camry
  5.  
  6. for (car in cars) { println car } // invokes Car toString method
  7.  
  8. class Car {
  9. make; model
  10. String toString() { "Car: make=${make}, model=${model}" }
  11. }

To locate list items starting from the end of the list, use negative indexes.

Empty lists can be created with []. For example,

cars = []

Items can be added to lists in two ways.

  1. cars.add car
  2. cars << car

Lists can be created from arrays with array.toList(). Arrays can be created from lists with list.toArray().

Groovy adds several methods to java.util.List.

count
This counts the elements in a list that are equal to a given object.
[1, 2, 3, 1].count(1) returns 2.
immutable
This creates an immutable copy of a collection using the static unmodifiableList method in java.util.Collections. For example ...
  1. list = [1, 2, 3].immutable()
  2. list.add 4 // throws java.lang.UnsupportedOperationException
intersect
This creates a list containing the common elements of two lists.
[1, 2, 3, 4].intersect([2, 4, 6]) returns [2, 4].
join
This concatenates list item toString values with a given string between each. For example, this places a caret delimiter between all the strings in a List.
['one', 'two', 'three'].join('^') returns "one^two^three".
sort
This sorts list elements and creates a new list. It accepts a java.util.Comparator or a closure for custom ordering.
  1. fruits = ['kiwi', 'strawberry', 'grape', 'banana']
  2. // The next line returns [banana, grape, kiwi, strawberry].
  3. sortedFruits = fruits.sort()
  4.  
  5. // The next line returns [kiwi, grape, banana, strawberry].
  6. sortedFruits =
  7. fruits.sort {l, r | return l.length() <=> r.length()}

The last call to sort above is an example of a method that takes a closure as a parameter. There are many methods in Groovy that do this.

Groovy Beans can easily be sorted on multiple properties. Suppose there is a Player bean with properties nameage and score. To sort a list of these beans called players based on age and then score,

players.sort { [it.age, it.score] }
min / max
These find the minimum or maximum list item or string character. They accept a java.util.Comparator or a closure for custom ordering. For example, these find the minimum and maximum number in a list.
[5, 9, 1, 6].min() returns 1.
[5, 9, 1, 6].max() returns 9.
reverse
This reverses the order of elements in a list or characters in a string.
[1, 2, 3].reverse() returns [3, 2, 1].
Groovy overloads the plus and minus operators for working with java.util.List objects.
plus
This creates a union of two lists, but duplicates are not removed.
[1, 2, 3] + [2, 3, 4] returns [1, 2, 3, 2, 3, 4].
minus
This removes all elements from the first list that are in the second.
[1, 2, 3, 4] - [2, 4, 6] returns [1, 3].
When the list items are not primitives, the equals method is used to compare them.

Groovy Maps

Groovy maps are instances of java.util.HashMap. They can be created using a comma-separated list of key/value pairs in square brackets. The keys and values are separated by a colon. For example,

  1. players = ['baseball':'Albert Pujols',
  2. 'golf':'Tiger Woods']
  3.  
  4. println players['golf'] // prints Tiger Woods
  5. println players.golf // prints Tiger Woods
  6.  
  7. for (player in players) {
  8. println "${player.value} plays ${player.key}"
  9. }
  10.  
  11. // This has the same result as the previous loop.
  12. players.each {player |
  13. println "${player.value} plays ${player.key}"
  14. }

Empty maps can be created with [:]. For example,

players = [:]

Groovy Switch

The Groovy switch statement takes any kind of object including Class, List, Range and Pattern. case statements compare values using the isCase method. Many overloaded versions of isCase are provided. Unless overloaded for specific types, isCase uses the equals method. When a case is followed by a class name, isCase uses instanceof. The isCase method can be overriden in your own classes.

Here's an example of a switch statement that operates on many different types of values.

  1. switch (x) {
  2. case 'Mark':
  3. println "got my name"
  4. break
  5. case 3..7:
  6. println 'got a number in the range 3 to 7 inclusive'
  7. break
  8. case ['Moe', 'Larry', 'Curly']:
  9. println 'got a Stooge name'
  10. break
  11. case java.util.Date:
  12. println 'got a Date object'
  13. break
  14. case ~"\\d{5}":
  15. println 'got a zip code'
  16. break
  17. default:
  18. println "got unexpected value ${x}"
  19. }

Groovy Ranges

Ranges are created by using the ".." and "..." operators. Here are some examples.

3..7 creates a range from 3 to 7
3...7 creates a range from 3 to 6
"A".."D" creates a range from "A" to "D"
"A"..."D" creates a range from "A" to "C"

Ranges are represented by objects from classes that implement the groovy.lang.Range interface which extends java.util.AbstractList. A Range is an immutable List. The Range interface adds getFrom and getTo methods to get lower and upper values. Two implementations of the Range interface are provided. groovy.lang.IntRange is used when bounds are integers. It adds a contains method to test whether a value is in the range. groovy.lang.ObjectRange is used when bounds are any other type. It also adds a contains method, but it is only useful when the objects used for the bounds implement java.lang.Comparable.

Ranges are useful in loops. See the examples in the next section.

Groovy Looping

Here are six ways to loop through a range of values.

for

 for (i in 1..1000) { println i }

while

i = 1
    while (i

each

    (1..1000).each { println it }

times

    1000.times { println it }
    // values go from 0 to 999

upto

    1.upto(1000) { println it }

step

    1.step(1001, 1) { println it }
    // values go from 1 to 1000;
    // stopping one before the parameter value

List/Map/String Methods That Accept a Closure

Several List, Map and String methods accept a closure as a parameter.

each
This iterates through collection items or string characters. It is an alternative to using java.util.Iterator that results in more compact code. For example, to print each number in a List
[5, 9, 1, 6].each {x | println x} 
or
[5, 9, 1, 6].each {println it}
collect
This transforms a collection or string into a new one. For example, to double each number in a List and create a new List
doubles = [5, 9, 1, 6].collect {x | x * 2} 
This sets doubles to [10, 18, 2, 12].
find
This finds the first occurrence of a collection item or string character that meets some criteria. For example, to find the first number in a list that is greater than 5
[5, 9, 1, 6].find {x | x > 5} returns 9.
findAll
This finds all occurrences of a collection item or string character that meet some criteria. For example, to find all the numbers in a list that are greater than 5
[5, 9, 1, 6].findAll {x | x > 5} returns [9, 6].
every
This determines whether every collection item or string character meets some criteria. For example, to determine whether all the numbers in a List are less than 7
[5, 9, 1, 6].every {x | x < 7} returns false.
any
This determines whether any collection item or string character meets some criteria. For example, to determine whether any of the numbers in a List are less than 7
[5, 9, 1, 6].any {x | x < 7} returns true.
inject
This passes a value into the first iteration and passes the result of each iteration into the next one. For example, to find 5 factorial (in an unusual way)
factorial = [2, 3, 4, 5].inject(1) {
  prevResult, x | prevResult * x
}

This closure is executed four times.
1) 1 * 2 
2) 2 * 3 
3) 6 * 4 
4) 24 * 5 
It returns 120.

File I/O

The ellipses (...) in the code examples below indicate omitted code.

Reading Lines From a File (2 options)

  1. file = new File('myFile.txt')
  2. file.eachLine { println it }
  3. lineList = file.readLines()

Reading Bytes From a File (2 options)

  1. file = new File('myFile.txt')
  2. file.eachByte { println it }
  3. byteList = file.readBytes()

Reading Files in a Directory

  1. dir = new File('directory-path')
  2. dir.eachFile { file | . . . }

Reading With Resource Closing

  1. file.withReader { reader | . . . }
  2. reader.withReader { reader | . . . }
  3. inputStream.withStream { is | . . . }

(These methods of reading from a Reader or InputStream insure that it gets closed regardless of whether an exception is thrown.)

Writing With Resource Closing

  1. file.withWriter { writer | . . . }
  2. file.withPrintWriter { pw | . . . }
  3. file.withOutputStream { os | . . . }
  4. writer.withWriter { writer | . . . }
  5. outputStream.withStream { os | . . . }

(These methods of writing to a Writer or OutputStream insure that it gets closed regardless of whether an exception is thrown.)

Overloaded Left Shift Operator

To append strings

s = 'foo'
s = s << 'bar'

To append to a StringBuffer

sb = new StringBuffer('foo')
sb << 'bar'

To add to lists

colors = ['red', 'green']
colors << 'blue'

To write to the end of streams

w = new File('myFile.txt').newWriter()
w << 'foo' << 'bar'
w.close()

Object Navigation

Object graphs can be walked with XPath-like syntax using the dot (".") operator. To avoid risk of NullPointerException, use the "->" operator instead of ".". For example,

  1. class Team {
  2. String name
  3. Person coach
  4. players = []
  5. }
  6.  
  7. class Person {
  8. String name
  9. }
  10.  
  11. p = new Person(name:'Mike Martz')
  12. t = new Team(name:'Rams', coach:p)
  13.  
  14. // The next line prints the same as team.getCoach().getName().
  15. println "coach = ${t.coach.name}"
  16.  
  17. t = new Team(name:'Blues')
  18.  
  19. // The next line returns null,
  20. // it doesn't throw NullPointerException.
  21. println "coach = ${t->coach->name}"
  22.  
  23. // The next line throws NullPointerException.
  24. println "coach = ${t.coach.name}"

Groovy Reflection

Suppose you want to get a Class object from an object. In Java this is done with someObject.getClass(). In Groovy, this is done with someObject.class.

Suppose you want to get a Class object from a class name. In both Java and Groovy this is done with SomeClass.class or Class.forName("pkg.SomeClass").

To print a list of methods in the Groovy class GString,

GString.class.methods.each { it.name }

To print a list of method names in the Java interface java.util.List,

java.util.List.class.methods.each { it.name }

Catching Unimplemented Methods

Classes can be written to catch calls to unimplemented methods. For example,

  1. o = new CatchCall()
  2.  
  3. // The next line prints "unknown method Mark called with [19]".
  4. println o.foo("Mark", 19)
  5.  
  6. class CatchCall {
  7. invokeMethod(String name, Object args) {
  8. try {
  9. return metaClass.invokeMethod(this, name, args)
  10. } catch (MissingMethodException e) {
  11. // Can insert logic here to deal with certain
  12. // method names and arguments in special ways.
  13. return "unknown method ${name} called with ${args}"
  14. }
  15. }
  16. }

Groovy Markup

Groovy Markup utilizes the invokeMethod method just described to catch calls to non-existent methods and convert them to "nodes". Parameters to the methods are treated as attributes of the nodes. Closures after the methods are treated as the content of the nodes. This has many uses including

In addition, custom builders can be created by extending the class groovy.util.BuilderSupport.

Generating HTML

Here's an example of using MarkupBuilder to generate HTML.

  1. import groovy.xml.MarkupBuilder
  2.  
  3. mb = new MarkupBuilder()
  4. mb.html() {
  5. head() {
  6. title("This is my title.")
  7. }
  8. body() {
  9. p("This is my paragraph.")
  10. }
  11. }
  12. println mb

This generates the following HTML.

  1. <html>
  2. <head>
  3. <title>This is my title.</title>
  4. </head>
  5. <body>
  6. <p>This is my paragraph.</p>
  7. </body>
  8. </html>

Generating XML

Here's an example of using MarkupBuilder to generate XML.

  1. import groovy.xml.MarkupBuilder;
  2.  
  3. mb = new MarkupBuilder()
  4. mb.autos() {
  5. auto(year:2001, color:'blue') {
  6. make('Toyota')
  7. model('Camry')
  8. }
  9. }
  10. println mb

This generates the following XML.

  1. <autos>
  2. <auto year='2001' color='blue'>
  3. <make>Toyota</make>
  4. <model>Camry</model>
  5. </auto>
  6. </autos>

Groovy SQL

Groovy makes JDBC easier. The class groovy.sql.Sql provides an easy way to execute a query and iterate through ResultSet rows. In the following example, MusicCollection is the name of a database (in this case, registered as an ODBC data source), Artists is the name of a table in that database, and Name is the name of a column in that table.

  1. import groovy.sql.Sql
  2.  
  3. dbURL = 'jdbc:odbc:MusicCollection'
  4. jdbcDriver = 'sun.jdbc.odbc.JdbcOdbcDriver'
  5. sql = Sql.newInstance(dbURL, jdbcDriver)
  6. sql.eachRow('select * from Artists') {
  7. println it.Name
  8. }

Groovlets

Groovlets are the Groovy alternative to Servlets and JSP. They provide the following implicit variables.

Here is an example Groovlet. This can be saved in a file with a name like SimpleGroovlet.groovy. It uses a "here-doc" to output HTML.

  1. out.println <<<EOS
  2. <html>
  3. <head>
  4. <title>My Simple Groovlet</title>
  5. </head>
  6. <body>
  7. <h1>My Simple Groovlet</h1>
  8. <p>Today is ${new java.util.Date()}.</p>
  9. </body>
  10. </html>
  11. EOS

GroovyServlet compiles Groovlets and caches them until they are changed. It automatically recompiles them if they are changed. GroovyServlet must be registered in web.xml.

Here's an example web.xml file showing only the part to register GroovyServlet.

  1. <?xml version="1.0"?>
  2. <!DOCTYPE web-app
  3. PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  4. "http://java.sun.com/dtd/web-app_2_3.dtd">
  5. <web-app>
  6. <servlet>
  7. <servlet-name>Groovy</servlet-name>
  8. <servlet-class>groovy.servlet.GroovyServlet</servlet-class>
  9. </servlet>
  10. <servlet-mapping>
  11. <servlet-name>Groovy</servlet-name>
  12. <url-pattern>*.groovy</url-pattern>
  13. </servlet-mapping>
  14. </web-app>

Groovlets can be deployed using Ant. The basic steps are

  1. Create a WAR with the following contents.
    • Groovlet source files (*.groovy) at the top
    • web.xml in the WEB-INF directory
    • groovy*.jar and asm*.jar files in the WEB-INF/lib directory
  2. Deploy the WAR to a servlet engine such as Tomcat.

Here's an example Ant build file for doing this.

build.properties

  1. build.dir=build
  2. src.dir=src
  3.  
  4. # Directory that contains Groovlets
  5. groovy.dir=${src.dir}/groovy
  6.  
  7. # Directory that contains web.xml
  8. web.dir=${src.dir}/web
  9.  
  10. # Path to WAR that will be produced
  11. war.file=${build.dir}/${ant.project.name}.war
  12.  
  13. # Where the WAR should be deployed
  14. webapps.dir=${env.TOMCAT_HOME}/webapps
  15.  
  16. # JARs that must be in the WAR
  17. asm.jar=${env.GROOVY_HOME}/lib/asm-1.4.1.jar
  18. groovy.jar=${env.GROOVY_HOME}/lib/groovy-1.0-beta-4-snapshot.jar

build.xml

  1. <project name="GroovletExample" default="deploy">
  2. <property environment="env"></property>
  3. <property file="build.properties"></property>
  4.  
  5. <target name="prepare">
  6. <mkdir dir="${build.dir}"></mkdir>
  7. </target>
  8.  
  9. <target name="war" depends="prepare"
  10. description="creates WAR file">
  11. <war destfile="${war.file}" webxml="${web.dir}/web.xml">
  12. <fileset dir="${groovy.dir}"></fileset>
  13. <lib file="${groovy.jar}"></lib>
  14. <lib file="${asm.jar}"></lib>
  15. </war>
  16. </target>
  17.  
  18. <target name="deploy" depends="war"
  19. description="deploys WAR file">
  20. <delete dir="${webapps.dir}/${ant.project.name}"></delete>
  21. <delete file="${webapps.dir}/${war.file}"></delete>
  22. <copy file="${war.file}" todir="${webapps.dir}"></copy>
  23. </target>
  24.  
  25. </project>

Once this example Groovlet is deployed, it can be displayed in a web browser by visiting a URL like http://localhost:8080/GroovletExample/SimpleGroovlet.groovy. GroovletExample is the name of the web app. SimpleGroovlet.groovy is the name of the Groovlet. This matches the url-pattern specified for GroovyServlet in web.xml.

Issues

Groovy isn't perfect yet. To view issues with Groovy, visit http://groovy.codehaus.org and click the Issue Tracker link. Here are some of the reported issues along with their assigned issue numbers.

Summary

So there it is ... a quick run through of some of the syntax and features of Groovy. Will the shortcuts provided over Java allow you to get more work done? Will you have more fun doing it? Will your code be easier or harder to understand? I'd love to hear your feedback. Email me at mark@ociweb.com.

References

Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret