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.
- Visit http://groovy.codehaus.org/.
- Click the "Download" link in the top navigation bar.
- Click the "this site" link.
- Click a release to download.
To install Groovy, follow these steps.
- Unzip the downloaded file.
- Set the
GROOVY_HOME
environment variable to directory that was unzipped. - 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.
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.
- It is a goal to eventually support all legal Java syntax, but this goal has not yet been achieved.
- Semicolons at ends of statements are optional.
- Parentheses around method parameters are optional unless there are no parameters or the meaning would be ambiguous. However, parentheses are required in constructor calls. Some people prefer to always use parentheses. This article tends to omit them when possible.
- Using "return" is sometimes optional. In methods that return a value, if the last statement before the closing brace is reached, its value is returned. In the future, Groovy may be changed to return value of last statement evaluated.
- Groovy properties and methods are public by default, not protected like in Java. See the discussion about Groovy Beans later.
- Classes in
java.lang
,groovy.lang
andgroovy.util
are automatically imported.
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.
- s = 'abcabc' // a java.lang.String
- method = 'indexOf'
- args = ['b', 2]
- 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.
- s = " This string
- spans three \"lines\"
- and contains two newlines."
-
- s = """ This string
- spans three "lines"
- and contains two newlines."""
-
- s = <<<EOS
- This string
- spans three "lines"
- and contains two newlines.
- 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')
returnstrue
. count
- This counts occurrences of a substring within a string.
'Groovy Tool'.count('oo')
returns2
. 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.
~"pattern"
creates a Pattern object and is equivalent toPattern.compile("pattern")
."text" =~ "pattern"
creates a Matcher object and is equivalent toPattern.compile("pattern").matcher("text")
."text" ==~ "pattern"
returns a boolean match indication and is equivalent toPattern.compile("pattern").matcher("text").matches()
.
See the javadoc of Pattern and Matcher for additional methods.
For example,
- pattern = "\\d{5}" // matches zip codes (5 digits)
- text = "63304" // a zip code
-
- println text ==~ pattern // prints "true"
-
- m = text =~ pattern
- println m.matches() // prints "true"
-
- // The next line requires a literal string for the pattern.
- // A variable can't used.
- p = ~"\\d{5}"
- m = p.matcher(text)
- 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,
- // These are loose statements.
- println 'loose statement'
- myMethod 'Mark', 19
- println new MyClass(a1:'Running', a2:26.2)
-
- // This is a method definition that
- // is not associated with a class.
- def myMethod(p1, p2) {
- println "myMethod: p1=${p1}, p2=${p2}"
- }
-
- // This is a definition of a class that
- // has two properties and one method.
- class MyClass {
- a1; a2
- String toString() { "MyClass: a1=${a1}, a2=${a2}" }
- }
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 Javaa 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,
- closure = { bill, tipPercentage | bill * tipPercentage / 100 }
- tip = closure.call(25.19, 15)
- 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.
- { x | println x }
- { 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.
- def List myFind(List list, Closure closure) {
- List newList = []
- for (team in list) {
- if (closure.call team) newList.add team
- }
- newList
- }
Here's an example of using this method.
- class Team { name; wins; losses }
-
- teams = []
- teams.add new Team(name:'Rams', wins:12 , losses:4)
- teams.add new Team(name:'Raiders', wins:4 , losses:12)
- teams.add new Team(name:'Packers', wins:10 , losses:6)
- teams.add new Team(name:'49ers', wins:7 , losses:9)
-
- winningTeams = myFind(teams) { it.wins > it.losses }
- 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.
- class Car {
- String make
- String model
- }
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.
- public class Car {
- private String make;
- private String model;
-
- public String getMake() {
- return make;
- }
-
- public String getModel() {
- return model;
- }
-
- public void setMake(String make) {
- this.make = make;
- }
-
- public void setModel(String model) {
- this.model = model;
- }
- }
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,
- cars = [new Car(make:'Honda', model:'Odyssey'),
- new Car(make:'Toyota', model:'Camry')]
-
- println cars[1] // refers to Camry
-
- for (car in cars) { println car } // invokes Car toString method
-
- class Car {
- make; model
- String toString() { "Car: make=${make}, model=${model}" }
- }
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.
- cars.add car
- 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)
returns2
. immutable
- This creates an immutable copy of a collection using the static
unmodifiableList
method injava.util.Collections
. For example ...
- list = [1, 2, 3].immutable()
- 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.
- fruits = ['kiwi', 'strawberry', 'grape', 'banana']
- // The next line returns [banana, grape, kiwi, strawberry].
- sortedFruits = fruits.sort()
-
- // The next line returns [kiwi, grape, banana, strawberry].
- sortedFruits =
- 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 name
, age
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()
returns1
.[5, 9, 1, 6].max()
returns9
. 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 withjava.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, theequals
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,
- players = ['baseball':'Albert Pujols',
- 'golf':'Tiger Woods']
-
- println players['golf'] // prints Tiger Woods
- println players.golf // prints Tiger Woods
-
- for (player in players) {
- println "${player.value} plays ${player.key}"
- }
-
- // This has the same result as the previous loop.
- players.each {player |
- println "${player.value} plays ${player.key}"
- }
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.
- switch (x) {
- case 'Mark':
- println "got my name"
- break
- case 3..7:
- println 'got a number in the range 3 to 7 inclusive'
- break
- case ['Moe', 'Larry', 'Curly']:
- println 'got a Stooge name'
- break
- case java.util.Date:
- println 'got a Date object'
- break
- case ~"\\d{5}":
- println 'got a zip code'
- break
- default:
- println "got unexpected value ${x}"
- }
Groovy Ranges
Ranges are created by using the ".." and "..." operators. Here are some examples.
3..7
creates a range from 3 to 73...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 (i in 1..1000) { println i }
i = 1 while (i
(1..1000).each { println it }
1000.times { println it } // values go from 0 to 999
1.upto(1000) { println it }
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 setsdoubles
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}
returns9
. 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}
returnsfalse
. 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}
returnstrue
. 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.
- file = new File('myFile.txt')
- file.eachLine { println it }
- lineList = file.readLines()
- file = new File('myFile.txt')
- file.eachByte { println it }
- byteList = file.readBytes()
- dir = new File('directory-path')
- dir.eachFile { file | . . . }
- file.withReader { reader | . . . }
- reader.withReader { reader | . . . }
- inputStream.withStream { is | . . . }
(These methods of reading from a Reader or InputStream insure that it gets closed regardless of whether an exception is thrown.)
- file.withWriter { writer | . . . }
- file.withPrintWriter { pw | . . . }
- file.withOutputStream { os | . . . }
- writer.withWriter { writer | . . . }
- 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
s = 'foo'
s = s << 'bar'
sb = new StringBuffer('foo')
sb << 'bar'
colors = ['red', 'green']
colors << 'blue'
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,
- class Team {
- String name
- Person coach
- players = []
- }
-
- class Person {
- String name
- }
-
- p = new Person(name:'Mike Martz')
- t = new Team(name:'Rams', coach:p)
-
- // The next line prints the same as team.getCoach().getName().
- println "coach = ${t.coach.name}"
-
- t = new Team(name:'Blues')
-
- // The next line returns null,
- // it doesn't throw NullPointerException.
- println "coach = ${t->coach->name}"
-
- // The next line throws NullPointerException.
- 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")
.
GString.class.methods.each { it.name }
java.util.List.class.methods.each { it.name }
Catching Unimplemented Methods
Classes can be written to catch calls to unimplemented methods. For example,
- o = new CatchCall()
-
- // The next line prints "unknown method Mark called with [19]".
- println o.foo("Mark", 19)
-
- class CatchCall {
- invokeMethod(String name, Object args) {
- try {
- return metaClass.invokeMethod(this, name, args)
- } catch (MissingMethodException e) {
- // Can insert logic here to deal with certain
- // method names and arguments in special ways.
- return "unknown method ${name} called with ${args}"
- }
- }
- }
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
- building generic, data structure trees (
NodeBuilder
) - building DOM trees (
DOMBuilder
) - firing SAX events (
SAXBuilder
) - creating strings of HTML or XML (
MarkupBuilder
) - executing Ant tasks (
AntBuilder
) - creating Swing user interfaces (
SwingBuilder
)
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.
- import groovy.xml.MarkupBuilder
-
- mb = new MarkupBuilder()
- mb.html() {
- head() {
- title("This is my title.")
- }
- body() {
- p("This is my paragraph.")
- }
- }
- println mb
This generates the following HTML.
- <html>
- <head>
- <title>This is my title.</title>
- </head>
- <body>
- <p>This is my paragraph.</p>
- </body>
- </html>
Generating XML
Here's an example of using MarkupBuilder
to generate XML.
- import groovy.xml.MarkupBuilder;
-
- mb = new MarkupBuilder()
- mb.autos() {
- auto(year:2001, color:'blue') {
- make('Toyota')
- model('Camry')
- }
- }
- println mb
This generates the following XML.
- <autos>
- <auto year='2001' color='blue'>
- <make>Toyota</make>
- <model>Camry</model>
- </auto>
- </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.
- import groovy.sql.Sql
-
- dbURL = 'jdbc:odbc:MusicCollection'
- jdbcDriver = 'sun.jdbc.odbc.JdbcOdbcDriver'
- sql = Sql.newInstance(dbURL, jdbcDriver)
- sql.eachRow('select * from Artists') {
- println it.Name
- }
Groovlets
Groovlets are the Groovy alternative to Servlets and JSP. They provide the following implicit variables.
out
- what is returned by theHttpServletResponse getWriter
methodrequest
- theHttpServletRequest
session
- theHttpSession
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.
- out.println <<<EOS
- <html>
- <head>
- <title>My Simple Groovlet</title>
- </head>
- <body>
- <h1>My Simple Groovlet</h1>
- <p>Today is ${new java.util.Date()}.</p>
- </body>
- </html>
- 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
.
- <?xml version="1.0"?>
- <!DOCTYPE web-app
- PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
- "http://java.sun.com/dtd/web-app_2_3.dtd">
- <web-app>
- <servlet>
- <servlet-name>Groovy</servlet-name>
- <servlet-class>groovy.servlet.GroovyServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>Groovy</servlet-name>
- <url-pattern>*.groovy</url-pattern>
- </servlet-mapping>
- </web-app>
Groovlets can be deployed using Ant. The basic steps are
- Create a WAR with the following contents.
- Groovlet source files (
*.groovy
) at the top web.xml
in theWEB-INF
directorygroovy*.jar
andasm*.jar
files in theWEB-INF/lib
directory
- Groovlet source files (
- Deploy the WAR to a servlet engine such as Tomcat.
Here's an example Ant build file for doing this.
- build.dir=build
- src.dir=src
-
- # Directory that contains Groovlets
- groovy.dir=${src.dir}/groovy
-
- # Directory that contains web.xml
- web.dir=${src.dir}/web
-
- # Path to WAR that will be produced
- war.file=${build.dir}/${ant.project.name}.war
-
- # Where the WAR should be deployed
- webapps.dir=${env.TOMCAT_HOME}/webapps
-
- # JARs that must be in the WAR
- asm.jar=${env.GROOVY_HOME}/lib/asm-1.4.1.jar
- groovy.jar=${env.GROOVY_HOME}/lib/groovy-1.0-beta-4-snapshot.jar
- <project name="GroovletExample" default="deploy">
- <property environment="env"></property>
- <property file="build.properties"></property>
-
- <target name="prepare">
- <mkdir dir="${build.dir}"></mkdir>
- </target>
-
- <target name="war" depends="prepare"
- description="creates WAR file">
- <war destfile="${war.file}" webxml="${web.dir}/web.xml">
- <fileset dir="${groovy.dir}"></fileset>
- <lib file="${groovy.jar}"></lib>
- <lib file="${asm.jar}"></lib>
- </war>
- </target>
-
- <target name="deploy" depends="war"
- description="deploys WAR file">
- <delete dir="${webapps.dir}/${ant.project.name}"></delete>
- <delete file="${webapps.dir}/${war.file}"></delete>
- <copy file="${war.file}" todir="${webapps.dir}"></copy>
- </target>
-
- </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.
- Primitive parameters to methods and closures aren't supported yet (128 & 133).
- Arrays of primitives aren't supported yet (119).
- Static primitive fields aren't supported yet (153).
- Chained assignment (x = y = 19) isn't supported yet (57).
*
imports aren't supported yet (84).- Compiler doesn't catch calls to non-existent methods on statically typed parameters (170).
- Nested classes aren't supported yet (69).
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
- [1] Groovy home page
http://groovy.codehaus.org
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.