SlideShare ist ein Scribd-Unternehmen logo
1 von 104
Downloaden Sie, um offline zu lesen
Submission631©ASERT2007
Agile 2007 - 1
Groovy Tutorial
Dr Paul King
ASERT, Australia
Agile 2007 - 2
Submission631©ASERT2007
Topics
Introduction
• Language Basics
• Closures
• Builders
• Data Access
• Other Features
• Testing with Groovy
• Further Integration
• Grails
• More Information
Agile 2007 - 3
Submission631©ASERT2007
What is Groovy?
• “Groovy is like a super version
of Java. It can leverage Java's
enterprise capabilities but also
has cool productivity features like closures,
DSL support, builders and dynamic typing.”
Groovy = Java – boiler plate code
+ optional dynamic typing
+ closures
+ domain specific languages
+ builders
+ metaprogramming
Agile 2007 - 4
Submission631©ASERT2007
Groovy Goodies Overview
• Fully object oriented
• Closures: reusable
and assignable
pieces of code
• Operators can be
overloaded
• Multimethods
• Literal declaration for
lists (arrays), maps,
ranges and regular
expressions
• GPath: efficient
object navigation
• GroovyBeans
• grep and switch
• Templates, builder,
swing, Ant, markup,
XML, SQL, XML-RPC,
Scriptom, Grails,
tests, Mocks
Agile 2007 - 5
Submission631©ASERT2007
Growing Acceptance
• A slow and steady start but now
gaining in momentum; growing in
maturity & mindshare
Groovy
Recipes
Pragmatic
Bookshelf
Agile 2007 - 6
Submission631©ASERT2007
The Landscape of JVM Languages
Java bytecode calls
for static types
Dynamic features call
for dynamic types
optional
static
types
The terms “Java Virtual Machine” and “JVM” mean a Virtual Machine for the Java™ platform.
Agile 2007 - 7
Submission631©ASERT2007
Groovy Starter
System.out.println("Hello, World!"); // optional semicolon,
println 'Hello, World!' // System.out, brackets,
// main() method
def name = 'Guillaume' // dynamic typing
println "$name, I'll get the car." // GString
String longer = """${name}, the car
is in the next row.""" // multi-line string
// with static typing
assert 0.5 == 1/2 // BigDecimal equals()
def printSize(obj) { // optional duck typing
print obj?.size() // safe dereferencing
}
def animals = ['ant', 'bee', 'cat'] // native list syntax
assert animals.every { pet -> // closure support
pet < 'dog' // overloading
}
A Better Java...
Agile 2007 - 8
Submission631©ASERT2007
import java.util.List;
import java.util.ArrayList;
class Erase {
private List filterLongerThan(List strings, int length) {
List result = new ArrayList();
for (int i = 0; i < strings.size(); i++) {
String s = (String) strings.get(i);
if (s.length() <= length) {
result.add(s);
}
}
return result;
}
public static void main(String[] args) {
List names = new ArrayList();
names.add("Ted"); names.add("Fred");
names.add("Jed"); names.add("Ned");
System.out.println(names);
Erase e = new Erase();
List shortNames = e.filterLongerThan(names, 3);
System.out.println(shortNames.size());
for (int i = 0; i < shortNames.size(); i++) {
String s = (String) shortNames.get(i);
System.out.println(s);
}
}
}
This code
is valid
Java and
valid Groovy
Based on an
example by
Jim Weirich
& Ted Leung
...A Better Java...
Agile 2007 - 9
Submission631©ASERT2007
import java.util.List;
import java.util.ArrayList;
class Erase {
private List filterLongerThan(List strings, int length) {
List result = new ArrayList();
for (int i = 0; i < strings.size(); i++) {
String s = (String) strings.get(i);
if (s.length() <= length) {
result.add(s);
}
}
return result;
}
public static void main(String[] args) {
List names = new ArrayList();
names.add("Ted"); names.add("Fred");
names.add("Jed"); names.add("Ned");
System.out.println(names);
Erase e = new Erase();
List shortNames = e.filterLongerThan(names, 3);
System.out.println(shortNames.size());
for (int i = 0; i < shortNames.size(); i++) {
String s = (String) shortNames.get(i);
System.out.println(s);
}
}
}
Do the
semicolons
add anything?
And shouldn’t
we us more
modern list
notation?
Why not
import common
libraries?
...A Better Java...
Agile 2007 - 10
Submission631©ASERT2007
class Erase {
private List filterLongerThan(List strings, int length) {
List result = new ArrayList()
for (String s in strings) {
if (s.length() <= length) {
result.add(s)
}
}
return result
}
public static void main(String[] args) {
List names = new ArrayList()
names.add("Ted"); names.add("Fred")
names.add("Jed"); names.add("Ned")
System.out.println(names)
Erase e = new Erase()
List shortNames = e.filterLongerThan(names, 3)
System.out.println(shortNames.size())
for (String s in shortNames) {
System.out.println(s)
}
}
}
...A Better Java...
Agile 2007 - 11
Submission631©ASERT2007
class Erase {
private List filterLongerThan(List strings, int length) {
List result = new ArrayList()
for (String s in strings) {
if (s.length() <= length) {
result.add(s)
}
}
return result
}
public static void main(String[] args) {
List names = new ArrayList()
names.add("Ted"); names.add("Fred")
names.add("Jed"); names.add("Ned")
System.out.println(names)
Erase e = new Erase()
List shortNames = e.filterLongerThan(names, 3)
System.out.println(shortNames.size())
for (String s in shortNames) {
System.out.println(s)
}
}
}
Do we need
the static types?
Must we always
have a main
method and
class definition?
How about
improved
consistency?
...A Better Java...
Agile 2007 - 12
Submission631©ASERT2007
def filterLongerThan(strings, length) {
def result = new ArrayList()
for (s in strings) {
if (s.size() <= length) {
result.add(s)
}
}
return result
}
names = new ArrayList()
names.add("Ted")
names.add("Fred")
names.add("Jed")
names.add("Ned")
System.out.println(names)
shortNames = filterLongerThan(names, 3)
System.out.println(shortNames.size())
for (s in shortNames) {
System.out.println(s)
}
...A Better Java...
Agile 2007 - 13
Submission631©ASERT2007
def filterLongerThan(strings, length) {
def result = new ArrayList()
for (s in strings) {
if (s.size() <= length) {
result.add(s)
}
}
return result
}
names = new ArrayList()
names.add("Ted")
names.add("Fred")
names.add("Jed")
names.add("Ned")
System.out.println(names)
shortNames = filterLongerThan(names, 3)
System.out.println(shortNames.size())
for (s in shortNames) {
System.out.println(s)
}
Shouldn’t we
have special
notation for lists?
And special
facilities for
list processing?
...A Better Java...
Agile 2007 - 14
Submission631©ASERT2007
def filterLongerThan(strings, length) {
return strings.findAll{ it.size() <= length }
}
names = ["Ted", "Fred", "Jed", "Ned"]
System.out.println(names)
shortNames = filterLongerThan(names, 3)
System.out.println(shortNames.size())
shortNames.each{ System.out.println(s) }
...A Better Java...
Agile 2007 - 15
Submission631©ASERT2007
def filterLongerThan(strings, length) {
return strings.findAll{ it.size() <= length }
}
names = ["Ted", "Fred", "Jed", "Ned"]
System.out.println(names)
shortNames = filterLongerThan(names, 3)
System.out.println(shortNames.size())
shortNames.each{ System.out.println(s) }
Is the method
now needed?
Easier ways to
use common
methods?
Are brackets
required here?
...A Better Java...
Agile 2007 - 16
Submission631©ASERT2007
names = ["Ted", "Fred", "Jed", "Ned"]
println names
shortNames = names.findAll{ it.size() <= 3 }
println shortNames.size()
shortNames.each{ println it }
["Ted", "Fred", "Jed", "Ned"]
3
Ted
Jed
Ned
...A Better Java
Agile 2007 - 17
Submission631©ASERT2007
names = ["Ted", "Fred", "Jed", "Ned"]
println names
shortNames = names.findAll{ it.size() <= 3 }
println shortNames.size()
shortNames.each{ println it }
import java.util.List;
import java.util.ArrayList;
class Erase {
private List filterLongerThan(List strings, int length) {
List result = new ArrayList();
for (int i = 0; i < strings.size(); i++) {
String s = (String) strings.get(i);
if (s.length() <= length) {
result.add(s);
}
}
return result;
}
public static void main(String[] args) {
List names = new ArrayList();
names.add("Ted"); names.add("Fred");
names.add("Jed"); names.add("Ned");
System.out.println(names);
Erase e = new Erase();
List shortNames = e.filterLongerThan(names, 3);
System.out.println(shortNames.size());
for (int i = 0; i < shortNames.size(); i++) {
String s = (String) shortNames.get(i);
System.out.println(s);
}
}
}
...
public void setModel(String model) {
this.model = model;
}
public int getFrame() {
return frame;
}
public String getSerialNo() {
return serialNo;
}
public void setSerialNo(String serialNo) {
this.serialNo = serialNo;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public BigDecimal getCost() {
return cost;
}
public void setCost(BigDecimal cost) {
this.cost = cost.setScale(3, BigDecimal.ROUND_HALF_UP);
}
}
Better JavaBeans...
Agile 2007 - 18
Submission631©ASERT2007
import java.math.BigDecimal;
public class BikeJavaBean {
private String manufacturer;
private String model;
private int frame;
private String serialNo;
private double weight;
private String status;
private BigDecimal cost;
public BikeJavaBean(String manufacturer, String model,
int frame, String serialNo,
double weight, String status) {
this.manufacturer = manufacturer;
this.model = model;
this.frame = frame;
this.serialNo = serialNo;
this.weight = weight;
this.status = status;
}
public String toString() {
return "Bike:‚ +
‚n manufacturer -- " + manufacturer +
"n model -- " + model +
"n frame -- " + frame +
"n serialNo -- " + serialNo +
"n";
}
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getModel() {
return model;
}
...
Modified example from: http://www.ibm.com/developerworks/library/j-pg09196.html
...
public void setModel(String model) {
this.model = model;
}
public int getFrame() {
return frame;
}
public String getSerialNo() {
return serialNo;
}
public void setSerialNo(String serialNo) {
this.serialNo = serialNo;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public BigDecimal getCost() {
return cost;
}
public void setCost(BigDecimal cost) {
this.cost = cost.setScale(3, BigDecimal.ROUND_HALF_UP);
}
}
...Better JavaBeans...
Agile 2007 - 19
Submission631©ASERT2007
import java.math.BigDecimal;
public class BikeJavaBean {
private String manufacturer;
private String model;
private int frame;
private String serialNo;
private double weight;
private String status;
private BigDecimal cost;
public BikeJavaBean(String manufacturer, String model,
int frame, String serialNo,
double weight, String status) {
this.manufacturer = manufacturer;
this.model = model;
this.frame = frame;
this.serialNo = serialNo;
this.weight = weight;
this.status = status;
}
public String toString() {
return "Bike:‚ +
‚n manufacturer -- " + manufacturer +
"n model -- " + model +
"n frame -- " + frame +
"n serialNo -- " + serialNo +
"n";
}
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getModel() {
return model;
}
...
Auto
getters?
Auto
setters?
Auto
construction?
... Better JavaBeans
Agile 2007 - 20
Submission631©ASERT2007
class BikeGroovyBean {
String manufacturer, model, serialNo, status
final Integer frame
Double weight
BigDecimal cost
public void setCost(BigDecimal newCost) {
cost = newCost.setScale(3, BigDecimal.ROUND_HALF_UP)
}
public String toString() {
return """Bike:
manufacturer -- $manufacturer
model -- $model
frame -- $frame
serialNo -- $serialNo
"""
}
}
Agile 2007 - 21
Submission631©ASERT2007
Topics
• Introduction
Language Basics
• Closures
• Builders
• Data Access
• Other Features
• Testing with Groovy
• Further Integration
• Grails
• More Information
Agile 2007 - 22
Submission631©ASERT2007
Strings…
• Several forms
– Single quotes for
simple strings
– Double quotes for
GStrings which
support variable
expansion
– Slashy strings
behave like
GStrings but
preserve
backslashes (great
for regex and
directory names)
– Multi-line versions
// normal strings
def firstname = 'Kate'
def surname= "Bush"
assert firstname * 2 == 'KateKate'
// GString
def fullname = "$firstname $surname"
assert fullname == 'Kate Bush'
assert fullname - firstname == ' Bush'
assert fullname.padLeft(10) ==
' Kate Bush'
// indexing (including ranges)
assert fullname[0..3] == firstname
assert fullname[-4..-1] == surname
assert fullname[5, 3..1] == 'Beta'
…Strings…
Agile 2007 - 23
Submission631©ASERT2007
// slashy string: (almost) no escaping
def path = /C:WindowsSystem32/
def plain = 'nrtbf$'
assert plain.size() == 7
def slashy = /nrtbf$/
assert slashy.size() == 14
// late binding trick with closures
fullname = "${-> firstname} $surname"
assert fullname == 'Kate Bush'
firstname = 'George'
surname = 'Clooney'
assert fullname == 'George Bush'
// Multi-line strings
def twister = '''
She sells, sea shells
By the sea shore'''
def lines =
twister.split('n')
assert lines.size() == 2
def address = """
$fullname
123 First Ave
New York
""".trim()
def lines =
address.split('n')
assert lines.size() == 3
println """
----------------------
| $fullname |
| 123 First Ave |
| New York |
----------------------
"""
…Strings
Agile 2007 - 24
Submission631©ASERT2007
// more substrings
string = 'hippopotamus'
assert string - 'hippo' - 'mus' + 'to' == 'potato'
assert string.replace('ppopotam','bisc') == 'hibiscus'
// processing characters
assert 'apple'.toList() == ['a', 'p', 'p', 'l', 'e']
//also: 'apple' as String[], 'apple'.split(''), 'apple'.each{}
string = "an apple a day"
assert string.toList().unique().sort().join() == ' adelnpy'
// reversing chars/words
assert 'string'.reverse() == 'gnirts'
string = 'Yoda said, "can you see this?"'
revwords = string.split(' ').toList().reverse().join(' ')
assert revwords == 'this?" see you "can said, Yoda'
words = ['bob', 'alpha', 'rotator', 'omega', 'reviver']
bigPalindromes = words.findAll{w -> w == w.reverse() && w.size() > 5}
assert bigPalindromes == ['rotator', 'reviver']
Numbers...
• Java Approach
– Supports primitive types and object types
– Has wrapper classes to allow conversion
– Java 5+ has autoboxing to hide difference
• Groovy Approach
– Treats everything as an object at the language level
• And does appropriate autoboxing under the covers when
integrating with Java
– BigDecimal used as the default type for non-Integers
– Operator overloading applies to all common
operations on numbers, e.g. 3 * 4 is the same as
3.multiply(4)
• You can use these operators for your own types, e.g. Person
* 3 calls the multiple method on your person object
Agile 2007 - 25
Submission631©ASERT2007
...Numbers
Agile 2007 - 26
Submission631©ASERT2007
def x = 3
def y = 4
assert x + y == 7
assert x.plus(y) == 7
assert x instanceof Integer
assert 0.5 == 1/2 // uses BigDecimal arithmetic as default
def a = 2 / 3 // 0.6666666666
def b = a.setScale(3, BigDecimal.ROUND_HALF_UP)
assert b.toString() == '0.667'
assert 4 + 3 == 7 // 4.plus(3)
assert 4 - 3 == 1 // 4.minus(3)
assert 4 * 3 == 12 // 4.multiply(12)
assert 4 % 3 == 1 // 4.mod(3)
assert 4 ** 3 == 64 // 4.power(3)
assert 4 / 3 == 1.3333333333 // 4.div(3)
assert 4.intdiv(3) == 1 // normal integer division
assert !(4 == 3) // !(4.equals(3))
assert 4 != 3 // ! 4.equals(3)
assert !(4 < 3) // 4.compareTo(3) < 0
assert !(4 <= 3) // 4.compareTo(3) <= 0
assert 4 > 3 // 4.compareTo(3) > 0
assert 4 >= 3 // 4.compareTo(3) >= 0
assert 4 <=> 3 == 1 // 4.compareTo(3)
Dates...
• Mostly same support as Java
• Use java.util.Date or java.util.Calendar
– Or java.sql.Date etc.
– Can use static imports to help
• Or 3rd party package like Joda Time
• Does have special support for changing
times:
date2 = date1 + 1.week – 3.days + 6.hours
• More utility methods expected for
inputting and outputting dates
• Potentially will change with JSR 310
Agile 2007 - 27
Submission631©ASERT2007
...Dates
Agile 2007 - 28
Submission631©ASERT2007
import static java.util.Calendar.getInstance as now
import org.codehaus.groovy.runtime.TimeCategory
import java.text.SimpleDateFormat
println now().time
def date = new Date() + 1
println date
use(TimeCategory) {
println new Date() + 1.hour + 3.weeks - 2.days
}
input = "1998-06-03"
df1 = new SimpleDateFormat("yyyy-MM-dd")
date = df1.parse(input)
df2 = new SimpleDateFormat("MMM/dd/yyyy")
println 'Date was ' + df2.format(date)
Thu Jun 28 10:10:34 EST 2007
Fri Jun 29 10:10:35 EST 2007
Tue Jul 17 11:10:35 EST 2007
Date was Jun/03/1998
Lists, Maps, Ranges
• Lists
– Special syntax for list literals
– Additional common methods (operator overloading)
• Maps
– Special syntax for map literals
– Additional common methods
• Ranges
– Special syntax for various kinds of ranges
Agile 2007 - 29
Submission631©ASERT2007
def list = [3, new Date(), 'Jan']
assert list + list == list * 2
def map = [a: 1, b: 2]
assert map['a'] == 1 && map.b == 2
def letters = 'a'..'z'
def numbers = 0..<10
Agile 2007 - 30
Submission631©ASERT2007
Lists
assert [1,2,3,4] == (1..4)
assert [1,2,3] + [1] == [1,2,3,1]
assert [1,2,3] << 1 == [1,2,3,1]
assert [1,2,3,1] - [1] == [2,3]
assert [1,2,3] * 2 == [1,2,3,1,2,3]
assert [1,[2,3]].flatten() == [1,2,3]
assert [1,2,3].reverse() == [3,2,1]
assert [1,2,3].disjoint([4,5,6])
assert [1,2,3].intersect([4,3,1]) == [3,1]
assert [1,2,3].collect{ it+3 } == [4,5,6]
assert [1,2,3,1].unique().size() == 3
assert [1,2,3,1].count(1) == 2
assert [1,2,3,4].min() == 1
assert [1,2,3,4].max() == 4
assert [1,2,3,4].sum() == 10
assert [4,2,1,3].sort() == [1,2,3,4]
assert [4,2,1,3].findAll{ it%2 == 0 } == [4,2]
Agile 2007 - 31
Submission631©ASERT2007
Maps…
def map = [a:1, 'b':2]
println map // ["a":1, "b":2]
println map.a // 1
println map['a'] // 1
println map.keySet() // ["a", "b"]
map = [:]
// extend the map through assignment
map[1] = 'a'; map[2] = 'b'
map[true] = 'p'; map[false] = 'q'
map[null] = 'x'; map['null'] = 'z'
assert map == [ 1:'a', 2:'b', (true):'p',
(false):'q', (null):'x', 'null':'z' ]
def sb = new StringBuffer()
[1:'a', 2:'b', 3:'c'].each{ k, v-> sb << "$k:$v, " }
assert sb.toString() == '1:a, 2:b, 3:c, '
map = [1:'a', 2:'b', 3:'c']
def string = map.collect{ k, v -> "$k:$v" }.join(', ')
assert string == '1:a, 2:b, 3:c'
Agile 2007 - 32
Submission631©ASERT2007
…Maps
assert [
[ name: 'Clark', city: 'London' ],
[ name: 'Sharma', city: 'London' ],
[ name: 'Maradona', city: 'LA' ],
[ name: 'Zhang', city: 'HK' ],
[ name: 'Ali', city: 'HK' ],
[ name: 'Liu', city: 'HK' ]
].groupBy { it.city } == [
London: [
[ name: 'Clark', city: 'London' ],
[ name: 'Sharma', city: 'London' ]
], LA: [
[ name: 'Maradona', city: 'LA' ]
], HK: [
[ name: 'Zhang', city: 'HK' ],
[ name: 'Ali', city: 'HK' ],
[ name: 'Liu', city: 'HK' ]
]
]
Agile 2007 - 33
Submission631©ASERT2007
Regular Expressions…
assert "Hello World!" =~ /Hello/ // Find operator
assert "Hello World!" ==~ /Hellob.*/ // Match operator
def p = ~/Hellob.*/ // Pattern operator
assert p.class.name == 'java.util.regex.Pattern'
// replace matches with calculated values
assert "1.23".replaceAll(/./){ ch ->
ch.next()
} == '2/34'
assert "1.23".replaceAll(/d/){ num ->
num.toInteger() + 1
} == '2.34'
assert "1.23".replaceAll(/d+/){ num ->
num.toInteger() + 1
} == '2.24'
Agile 2007 - 34
Submission631©ASERT2007
…Regular Expressions
str = 'groovy.codehaus.org and www.aboutgroovy.com'
re = '''(?x) # to enable whitespace and comments
( # capture the hostname in $1
(?: # these parens for grouping only
(?! [-_] ) # neither underscore nor dash lookahead
[w-] + # hostname component
. # and the domain dot
) + # now repeat whole thing a few times
[A-Za-z] # next must be a letter
[w-] + # now trailing domain part
) # end of $1 capture
'''
finder = str =~ re
out = str
(0..<finder.count).each{
adr = finder[it][0]
out = out.replaceAll(adr,
"$adr [${InetAddress.getByName(adr).hostAddress}]")
}
println out
// => groovy.codehaus.org [63.246.7.187]
// and www.aboutgroovy.com [63.246.7.76]
if (1) // ...
if (object) // ...
if (list) // ...
for (item in iterable) { }
myMap.each { key, value ->
println "$key : $value"
}
// if (condition)...
// else if (condition) ...
// else ...
// throw, catch, finally
// for, while
// eachWithIndex, eachLine, ...
Agile 2007 - 35
Submission631©ASERT2007
Control Structures
switch (10) {
case 0 : /* F */ ; break
case 0..9 : // F
case [8,9,11] : // F
case Float : // F
case {it % 3 == 0} : // F
case ~/../ : // T
default : // F
}
// implement
// boolean isCase(candidate)
Agile 2007 - 36
Submission631©ASERT2007
GroovyBeans and GPath
class Dir {
String name
List dirs
}
def root = new Dir (name: '/', dirs: [
new Dir (name: 'a'),
new Dir (name: 'b')
])
assert root.dirs[0].name == 'a'
assert root.dirs.name == ['a', 'b']
assert root.dirs.name*.size() == [1, 1]
def expected = ['getName', 'setName', 'getDirs', 'setDirs']
def accessorMethods = Dir.methods.name.grep(~/(g|s)et.*/)
assert accessorMethods.intersect(expected) == expected
// find, findAll, grep, every, any, …
Static Imports
Agile 2007 - 37
Submission631©ASERT2007
import static java.awt.Color.LIGHT_GRAY
import static Boolean.FALSE as F
import static Calendar.getInstance as now
println LIGHT_GRAY // => java.awt.Color[r=192,g=192,b=192]
println !F // => true
println now().time // => Sun Apr 29 11:12:43 EST 2007
import static Integer.*
println "Integers are between $MIN_VALUE and $MAX_VALUE"
// => Integers are between -2147483648 and 2147483647
def toHexString(int val, boolean upperCase) {
def hexval = upperCase ?
toHexString(val).toUpperCase() : toHexString(val)
return '0x' + hexval
}
println toHexString(15, true) // => 0xF
println toHexString(15, false) // => 0xf
import static Math.*
assert cos(2 * PI) == 1.0
Works with Java 1.4
Slightly more powerful
than Java equivalent
Agile 2007 - 38
Submission631©ASERT2007
Topics
• Introduction
• Language Basics
Closures
• Builders
• Data Access
• Other Features
• Testing with Groovy
• Further Integration
• Grails
• More Information
Using Closures...
• Traditional mainstream languages
– Data can be stored in variables, passed around,
combined in structured ways to form more complex
data; code stays put where it is defined
• Languages supporting closures
– Data and code can be stored in variables, passed
around, combined in structured ways to form more
complex algorithms and data
Agile 2007 - 39
Submission631©ASERT2007
doubleNum = { num -> num * 2 }
println doubleNum(3) // => 6
processThenPrint = { num, closure ->
num = closure(num); println "num is $num"
}
processThenPrint(3, doubleNum) // => num is 6
processThenPrint(10) { it / 2 } // => num is 5
...Using Closures...
Agile 2007 - 40
Submission631©ASERT2007
import static Math.*
piA = { 22 / 7 }
piB = { 333/106 }
piC = { 355/113 }
piD = { 0.6 * (3 + sqrt(5)) }
piE = { 22/17 + 37/47 + 88/83 }
piF = { sqrt(sqrt(2143/22)) }
howCloseToPI = { abs(it.value() - PI) }
algorithms = [piA:piA, piB:piB, piC:piC,
piD:piD, piE:piE, piF:piF]
findBestPI(algorithms)
def findBestPI(map) {
map.entrySet().sort(howCloseToPI).each { entry ->
def diff = howCloseToPI(entry)
println "Algorithm $entry.key differs by $diff"
}
}
Algorithm piE differs by 1.0206946399193839E-11
Algorithm piF differs by 1.0070735356748628E-9
Algorithm piC differs by 2.668102068170697E-7
Algorithm piD differs by 4.813291008076703E-5
Algorithm piB differs by 8.321958979307098E-5
Algorithm piA differs by 0.001264489310206951
...Using Closures...
• Used for many things in Groovy:
• Iterators
• Callbacks
• Higher-order functions
• Specialized control structures
• Dynamic method definition
• Resource allocation
• Threads
• Continuations
Agile 2007 - 41
Submission631©ASERT2007
def houston(Closure doit) {
(10..1).each { count ->
doit(count)
}
}
houston { println it }
new File('/x.txt').eachLine {
println it
}
3.times { println 'Hi' }
[0, 1, 2].each { number ->
println number
}
[0, 1, 2].each { println it}
def printit = { println it }
[0, 1, 2].each printit
Agile 2007 - 42
Submission631©ASERT2007
...Using Closures...
map = ['a': 1, 'b': 2]
map.each {key, value -> map[key] = value * 2}
assert map == ['a': 2, 'b': 4]
doubler = {key, value -> map[key] = value * 2}
map.each(doubler)
assert map == ['a': 4, 'b': 8]
def doubleMethod(entry) {
map[entry.key] = entry.value * 2
}
doubler = this.&doubleMethod
map.each(doubler)
assert map == ['a': 8, 'b': 16]
Agile 2007 - 43
Submission631©ASERT2007
...Using Closures...
assert [1, 2, 3].grep{ it < 3 } == [1, 2]
assert [1, 2, 3].any{ it % 2 == 0 }
assert [1, 2, 3].every{ it < 4 }
assert (1..9).collect{it}.join() == '123456789'
assert (1..4).collect{it * 2}.join() == '2468'
def add = { x, y -> x + y }
def mult = { x, y -> x * y }
assert add(1, 3) == 4
assert mult(1, 3) == 3
def min = { x, y -> [x, y].min() }
def max = { x, y -> [x, y].max() }
def triple = mult.curry(3); assert triple(2) == 6
def atLeastTen = max.curry(10)
assert atLeastTen(5) == 10
assert atLeastTen(15) == 15
Agile 2007 - 44
Submission631©ASERT2007
...Using Closures
def pairWise(list, Closure invoke) {
if (list.size() < 2) return []
def next = invoke(list[0], list[1])
return [next] + pairWise(list[1..-1], invoke)
}
// using min, max, etc. From previous slide
assert pairWise(1..5, add) == [3, 5, 7, 9]
assert pairWise(1..5, mult) == [2, 6, 12, 20]
assert pairWise(1..5, min) == [1, 2, 3, 4]
assert pairWise(1..5, max) == [2, 3, 4, 5]
assert 'cbaxabc' == ['a', 'b', 'c'].inject('x') {
result, item -> item + result + item
}
Agile 2007 - 45
Submission631©ASERT2007
Topics
• Introduction
• Language Basics
• Closures
Builders
• Data Access
• Other Features
• Testing with Groovy
• Further Integration
• Grails
• More Information
Agile 2007 - 46
Submission631©ASERT2007
Builder Pattern Inclusive
import groovy.xml.*
def page = new MarkupBuilder()
page.html {
head { title 'Hello' }
body {
ul {
for (count in 1..10) {
li "world $count"
} } } }
• Create new builder
• Call pretended methods
(html, head, ...)
• Arguments are Closures
• Builder code looks very
declarative but is ordinary
Groovy program code and
can contain any kind of
logic
• Builder pattern from the GoF at the syntax-level
• Represents easily any nested tree-structured data
NodeBuilder, DomBuilder,
SwingBuilder, AntBuilder, …
Agile 2007 - 47
Submission631©ASERT2007
SwingBuilder
import java.awt.FlowLayout
builder = new groovy.swing.SwingBuilder()
langs = ["Groovy", "Ruby", "Python", "Pnuts"]
gui = builder.frame(size: [290, 100],
title: 'Swinging with Groovy!’) {
panel(layout: new FlowLayout()) {
panel(layout: new FlowLayout()) {
for (lang in langs) {
checkBox(text: lang)
}
}
button(text: 'Groovy Button', actionPerformed: {
builder.optionPane(message: 'Indubitably Groovy!').
createDialog(null, 'Zen Message').show()
})
button(text: 'Groovy Quit',
actionPerformed: {System.exit(0)})
}
}
gui.show()
Source: http://www.ibm.com/developerworks/java/library/j-pg04125/
Agile 2007 - 48
Submission631©ASERT2007
AntBuilder
def ant = new AntBuilder()
ant.echo("hello") // lets just call one task
// create a block of Ant using the builder pattern
ant.sequential {
myDir = "target/AntTest/"
mkdir(dir: myDir)
copy(todir: myDir) {
fileset(dir: "src/test") {
include(name: "**/*.groovy")
}
}
echo("done")
}
// now lets do some normal Groovy again
file = new File("target/test/AntTest.groovy")
assert file.exists()
Agile 2007 - 49
Submission631©ASERT2007
Using AntLibs: Maven Ant Tasks & AntUnit…
def ant = new AntBuilder()
items = [[groupId:'jfree', artifactId:'jfreechart', version:'1.0.5'],
[groupId:'jfree', artifactId:'jcommon', version:'1.0.9']]
def mvn = new AntLibHelper(ant:ant, namespace:'org.apache.maven.artifact.ant')
def antunit = new AntLibHelper(ant:ant, namespace:'org.apache.ant.antunit')
// download artifacts with Maven Ant tasks
mvn.dependencies(filesetId:'artifacts') { items.each{ dependency(it) } }
// print out what we downloaded
ant.fileScanner { fileset(refid:'artifacts') }.each { println it }
// use AntUnit to confirm expected files were downloaded
def prefix = System.properties.'user.home' + '/.m2/repository'
items.each { item ->
def g = item.groupId
def a = item.artifactId
def v = item.version
antunit.assertFileExists(file:"$prefix/$g/$a/$v/$a-${v}.jar")
}
Agile 2007 - 50
Submission631©ASERT2007
…Using AntLibs: Maven Ant Tasks & AntUnit
...
class AntLibHelper {
def namespace, ant
Object invokeMethod(String name, Object params) {
ant."antlib:$namespace:$name"(*params)
}
}
/* =>
Downloading: jfree/jfreechart/1.0.5/jfreechart-1.0.5.pom
Transferring 1K
Downloading: jfree/jcommon/1.0.9/jcommon-1.0.9.pom
Transferring 1K
Downloading: jfree/jfreechart/1.0.5/jfreechart-1.0.5.jar
Transferring 1157K
Downloading: jfree/jcommon/1.0.9/jcommon-1.0.9.jar
Transferring 298K
C:UsersPaul.m2repositoryjfreejcommon1.0.9jcommon-1.0.9.jar
C:UsersPaul.m2repositoryjfreejfreechart1.0.5jfreechart-1.0.5.jar
*/
Agile 2007 - 51
Submission631©ASERT2007
Topics
• Introduction
• Language Basics
• Closures
• Builders
Data Access
Objects
XML
Databases
• Other Features
• Testing with Groovy
• Further Integration
• Grails
• More Information
Uniform Data Access
• Data Access to Objects, XML, SQL
Databases has some differences and
some similarities
– Differences in detail about how to set up structures
– Similarities in how to process data within resulting
structures
Agile 2007 - 52
Submission631©ASERT2007
Agile 2007 - 53
Submission631©ASERT2007
Object Data Access…
import java.text.SimpleDateFormat
class Athlete {
def firstname, lastname, gender, country, dateOfBirth
}
def asDate(dateStr) {
new SimpleDateFormat("yyyy-MM-dd").parse(dateStr)
}
def athletes = [
new Athlete(firstname: 'Paul', lastname: 'Tergat',
dateOfBirth: '1969-06-17', gender: 'M', country: 'KEN'),
new Athlete(firstname: 'Khalid', lastname: 'Khannouchi',
dateOfBirth: '1971-12-22', gender: 'M', country: 'USA'),
new Athlete(firstname: 'Sammy', lastname: 'Korir',
dateOfBirth: '1971-12-12', gender: 'M', country: 'KEN'),
new Athlete(firstname: 'Ronaldo', lastname: 'da Costa',
dateOfBirth: '1970-06-07', gender: 'M', country: 'BRA'),
new Athlete(firstname: 'Paula', lastname: 'Radcliffe',
dateOfBirth: '1973-12-17', gender: 'F', country: 'GBR')
]
Agile 2007 - 54
Submission631©ASERT2007
…Object Data Access
def bornSince70 = {
asDate(it.dateOfBirth) > asDate('1970-1-1') }
def excludingKh = { it.lastname <= 'Kg' ||
it.lastname >= 'Ki' }
def byGenderDescThenByCountry = { a, b ->
a.gender == b.gender ?
a.country <=> b.country : b.gender <=> a.gender }
def someYoungsters = athletes.
findAll(bornSince70).
findAll(excludingKh).
sort(byGenderDescThenByCountry)
someYoungsters.each {
def name = "$it.firstname $it.lastname".padRight(25)
println "$name ($it.gender) $it.country $it.dateOfBirth"
}
Agile 2007 - 55
Submission631©ASERT2007
Xml Data Access…
import java.text.SimpleDateFormat
import com.thoughtworks.xstream.XStream
class Athlete {
String firstname, lastname, gender, country, dateOfBirth
}
def asDate(dateStr) { new SimpleDateFormat("yyyy-MM-dd").parse(dateStr) }
def athleteList = [
new Athlete(firstname: 'Paul', lastname: 'Tergat‘,
dateOfBirth: '1969-06-17', gender: 'M', country: 'KEN'),
new Athlete(firstname: 'Khalid', lastname: 'Khannouchi',
dateOfBirth: '1971-12-22', gender: 'M', country: 'USA'),
new Athlete(firstname: 'Sammy', lastname: 'Korir',
dateOfBirth: '1971-12-12', gender: 'M', country: 'KEN'),
new Athlete(firstname: 'Ronaldo', lastname: 'da Costa',
dateOfBirth: '1970-06-07', gender: 'M', country: 'BRA'),
new Athlete(firstname: 'Paula', lastname: 'Radcliffe',
dateOfBirth: '1973-12-17', gender: 'F', country: 'GBR')
]
// create XML as input
def input = new XStream().toXML(athleteList)
Agile 2007 - 56
Submission631©ASERT2007
…Xml Data Access
def athletes = new XmlSlurper().parseText(input).Athlete
def bornSince70 = {
asDate(it.dateOfBirth.text()) > asDate('1970-1-1') }
def excludingKh = { it.lastname.text() <= 'Kg' ||
it.lastname.text() >= 'Ki' }
def byGenderDescThenByCountry = { a, b ->
a.gender.text() == b.gender.text() ?
a.country.text() <=> b.country.text() :
b.gender.text() <=> a.gender.text() }
def someYoungsters = athletes.
findAll(bornSince70).findAll(excludingKh).list().
sort(byGenderDescThenByCountry)
someYoungsters.each {
def name = "$it.firstname $it.lastname".padRight(25)
println "$name ($it.gender) $it.country $it.dateOfBirth"
}
Agile 2007 - 57
Submission631©ASERT2007
Sql Data Access…
import groovy.sql.Sql
dbHandle = null
def getDb() {
if (dbHandle) return dbHandle
def source = new org.hsqldb.jdbc.jdbcDataSource()
source.database = 'jdbc:hsqldb:mem:GIA'
source.user = 'sa'
source.password = ''
dbHandle = new Sql(source)
return dbHandle
}
db.execute '''
DROP INDEX athleteIdx IF EXISTS;
DROP TABLE Athlete IF EXISTS;
CREATE TABLE Athlete (
athleteId INTEGER GENERATED BY DEFAULT AS IDENTITY,
firstname VARCHAR(64),
lastname VARCHAR(64),
country VARCHAR(3),
gender CHAR(1),
dateOfBirth DATE
);
CREATE INDEX athleteIdx ON Athlete (athleteId);
'''
Agile 2007 - 58
Submission631©ASERT2007
…Sql Data Access…
def athleteList = [
[firstname: 'Paul', lastname: 'Tergat',
dateOfBirth: '1969-06-17', gender: 'M', country: 'KEN'],
[firstname: 'Khalid', lastname: 'Khannouchi',
dateOfBirth: '1971-12-22', gender: 'M', country: 'USA'],
[firstname: 'Sammy', lastname: 'Korir',
dateOfBirth: '1971-12-12', gender: 'M', country: 'KEN'],
[firstname: 'Ronaldo', lastname: 'da Costa',
dateOfBirth: '1970-06-07', gender: 'M', country: 'BRA'],
[firstname: 'Paula', lastname: 'Radcliffe',
dateOfBirth: '1973-12-17', gender: 'F', country: 'GBR']
]
def athletes = db.dataSet('Athlete')
athleteList.each {a -> athletes.add(a)}
def bornSince70 = { it.dateOfBirth > '1970-1-1' }
def excludingKh = { it.lastname <= 'Kg' || it.lastname >= 'Ki' }
def someYoungsters = athletes.
findAll(bornSince70).
findAll(excludingKh).
sort { it.gender }.reverse(). // * experimental
sort { it.country } // * experimental
println someYoungsters.sql + 'n' + someYoungsters.parameters
someYoungsters.each {
def name = "$it.firstname $it.lastname".padRight(25)
println "$name ($it.gender) $it.country $it.dateOfBirth"
}
/* =>
select * from Athlete where dateOfBirth > ? and (lastname <= ? or lastname
>= ?) order by gender DESC, country
["1970-1-1", "Kg", "Ki"]
Ronaldo da Costa (M) BRA 1970-06-07
Sammy Korir (M) KEN 1971-12-12
Paula Radcliffe (F) GBR 1973-12-17 */
Agile 2007 - 59
Submission631©ASERT2007
…Sql Data Access
Agile 2007 - 60
Submission631©ASERT2007
More Details: XmlSlurper…
• Features
– One-line parsing
– GPath Syntax
– Efficient lazy evaluation
static def CAR_RECORDS = '''
<records>
<car name='HSV Maloo' make='Holden' year='2006'>
<country>Australia</country>
<record type='speed'>Production Pickup Truck with speed of 271kph</record>
</car>
<car name='P50' make='Peel' year='1962'>
<country>Isle of Man</country>
<record type='size'>Smallest Street-Legal Car at 99cm wide and 59 kg in weight</record>
</car>
<car name='Royale' make='Bugatti' year='1931'>
<country>France</country>
<record type='price'>Most Valuable Car at $15 million</record>
</car>
</records>
'''
Agile 2007 - 61
Submission631©ASERT2007
…More Details: XmlSlurper
def records = new XmlSlurper().parseText(XmlExamples.CAR_RECORDS)
// 3 records in total
assert 3 == records.car.size()
// 10 nested nodes
assert 10 == records.depthFirst().collect{ it }.size()
// test properties of the first record
def firstRecord = records.car[0]
assert 'car' == firstRecord.name()
assert 'Holden' == firstRecord.@make.toString()
assert 'Australia' == firstRecord.country.text()
// 2 cars have an 'e' in the make
assert 2 == records.car.findAll{ it.@make.toString().contains('e') }.size()
// 2 cars have an 'e' in the make
assert 2 == records.car.findAll{ it.@make =~ '.*e.*' }.size()
// makes of cars that have an 's' followed by an 'a' in the country
assert ['Holden', 'Peel'] == records.car.findAll{ it.country =~ '.*s.*a.*' }.@make.collect{ it.toString
// types of records
assert ['speed', 'size', 'price'] == records.depthFirst().grep{ it.@type != '' }.'@type'*.toString()
assert ['speed', 'size', 'price'] == records.'**'.grep{ it.@type != '' }.'@type'*.toString()
// check parent() operator
def countryOne = records.car[1].country
assert 'Peel' == countryOne.parent().@make.toString()
assert 'Peel' == countryOne.'..'.@make.toString()
// names of cars with records sorted by year
def names = records.car.list().sort{ it.@year.toInteger() }.'@name'*.toString()
assert ['Royale', 'P50', 'HSV Maloo'] == names
Agile 2007 - 62
Submission631©ASERT2007
More Details: Working with Databases
• Using standard SQL statements
• Using DataSets
import groovy.sql.Sql
def foo = 'cheese'
def sql = Sql.newInstance("jdbc:mysql://localhost:3306/mydb", "user",
"pswd", "com.mysql.jdbc.Driver")
sql.eachRow("select * from FOOD where type=${foo}") {
println "Gromit likes ${it.name}"
}
import groovy.sql.Sql
def sql = Sql.newInstance("jdbc:mysql://localhost:3306/mydb", "user",
"pswd", "com.mysql.jdbc.Driver")
def food = sql.dataSet('FOOD')
def cheese = food.findAll { it.type == 'cheese' }
cheese.each { println "Eat ${it.name}" }
DataSets and Lazy Evaluation
Agile 2007 - 63
Submission631©ASERT2007
athleteSet = db.dataSet('Athlete')
youngsters = athleteSet.findAll{ it.dateOfBirth > '1970-1-1'}
paula = youngsters.findAll{ it.firstname == 'Paula'}
println paula.sql
// =>
// select * from Athlete where dateOfBirth > ? and firstname = ?
println paula.parameters
// =>
// [1970-1-1, Paula]
paula.each { println it.lastname } // database called here
// =>
// Radcliffe
Agile 2007 - 64
Submission631©ASERT2007
Topics
• Introduction
• Language Basics
• Closures
• Builders
• Data Access
Other Features
• Testing with Groovy
• Further Integration
• Grails
• More Information
Agile 2007 - 65
Submission631©ASERT2007
JMX
import javax.management.remote.*
import javax.management.*
import javax.naming.Context
def urlRuntime = '/jndi/weblogic.management.mbeanservers.runtime'
def urlBase = 'service:jmx:t3://localhost:7001'
def serviceURL = new JMXServiceURL(urlBase + urlRuntime)
def h = new Hashtable()
h.put(Context.SECURITY_PRINCIPAL, 'weblogic')
h.put(Context.SECURITY_CREDENTIALS, 'weblogic')
h.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES,
'weblogic.management.remote')
def server = JMXConnectorFactory.connect(serviceURL, h).mBeanServerConnection
def domainName = new ObjectName('com.bea:Name=RuntimeService,' +
'Type=weblogic.management.mbeanservers.runtime.RuntimeServiceMBean')
def rtName = server.getAttribute(domainName, 'ServerRuntime')
def rt = new GroovyMBean(server, rtName)
println "Server: name=$rt.Name, state=$rt.State, version=$rt.WeblogicVersion"
def destFilter = Query.match(Query.attr('Type'), Query.value(
'JMSDestinationRuntime'))
server.queryNames(new ObjectName('com.bea:*'), destFilter).each {name ->
def jms = new GroovyMBean(server, name)
println "JMS Destination: name=$jms.Name, type=$jms.DestinationType" +
", messages=$jms.MessagesReceivedCount"
}
Agile 2007 - 66
Submission631©ASERT2007
ExpandoMetaClass…
String.metaClass.swapCase = {->
def sb = new StringBuffer()
delegate.each {
sb << (Character.isUpperCase(it as char) ?
Character.toLowerCase(it as char) :
Character.toUpperCase(it as char))
}
sb.toString()
}
List.metaClass.sizeDoubled = {-> delegate.size() * 2 }
LinkedList list = []
list << 1
list << 2
assert 4 == list.sizeDoubled()
Agile 2007 - 67
Submission631©ASERT2007
…ExpandoMetaClass
class Person {
String name
}
class MortgageLender {
def borrowMoney() {
"buy house"
}
}
def lender = new MortgageLender()
Person.metaClass.buyHouse = lender.&borrowMoney
def p = new Person()
assert "buy house" == p.buyHouse()
Agile 2007 - 68
Submission631©ASERT2007
Constraint Programming
// require(url:'http://www.alice.unibo.it/tuProlog/', jar:'tuprolog.jar‘, version:'2.1')
import alice.tuprolog.*
/** Pretty Printing */
def pprint(soln) {
println soln.isSuccess() ?
"$soln.query = $soln.solution" :
'no solution found'
}
/** Prolog clauses */
def getTheory() {
new Theory(""“
parent(pam, bob).
parent(tom, bob).
parent(tom, liz).
parent(bob, ann).
parent(bob, pat).
parent(pat, jim).
female(pam).
male(tom).
male(bob).
female(liz).
female(pat).
female(ann).
male(jim).
offspring(X,Y) :- parent(Y,X).
...
mother(X,Y) :-
parent(X,Y), female(X).
father(X,Y) :-
parent(X,Y), male(X).
grandparent(X,Z) :-
parent(X,Y),
parent(Y,Z).
grandmother(X,Y) :-
grandparent(X,Y),
female(X).
grandfather(X,Y) :-
grandparent(X,Y),
male(X).
sister(X,Y) :-
parent(Z,X),
parent(Z,Y),
female(X).
brother(X,Y) :-
parent(Z,X),
parent(Z,Y),
male(X).
ancestor(X,Y) :- parent(X,Y).
ancestor(X,Z) :-
parent(X,Y),
ancestor(Y,Z).
""")
}
…
def engine = new Prolog()
engine.theory = theory
pprint engine.solve('ancestor(tom,X).')
while(engine.hasOpenAlternatives()) {
pprint engine.solveNext()
}
ancestor(tom,X) = ancestor(tom,bob)
ancestor(tom,X) = ancestor(tom,liz)
ancestor(tom,X) = ancestor(tom,ann)
ancestor(tom,X) = ancestor(tom,pat)
ancestor(tom,X) = ancestor(tom,jim)
no solution found
Agile 2007 - 69
Submission631©ASERT2007
Functional Programming
def fac(n) { n == 0 ? 1 : n * fac(n - 1) }
assert 24 == fac(4)
// now define and use infinite streams
def integers(n) { cons(n, { integers(n+1) }) }
def naturalnumbers = integers(1)
assert '1 2 3 4 5 6 7 8 9 10' ==
naturalnumbers.take(10).join(' ')
def evennumbers =
naturalnumbers.filter{ it % 2 == 0 }
assert '2 4 6 8 10 12 14 16 18 20' ==
evennumbers.take(10).join(' ')
Agile 2007 - 70
Submission631©ASERT2007
Using XStream - Vanilla
// require(groupId:'com.thoughtworks.xstream', artifactId:'xstream',
version:'1.2.2')
// require(groupId:'xpp3', artifactId:'xpp3_min', version:'1.1.3.4.O')
import com.thoughtworks.xstream.*
class Staff {
String firstname, lastname, position
}
def stream = new XStream()
def msg = new Staff(firstname:'John',
lastname:'Connor',
position:'Resistance Leader')
println stream.toXML(msg)
<Staff>
<firstname class="string">John</firstname>
<lastname class="string">Connor</lastname>
<position class="string">Resistance Leader</position>
</Staff>
Agile 2007 - 71
Submission631©ASERT2007
Using XStream - Annotations
import com.thoughtworks.xstream.*
import com.thoughtworks.xstream.annotations.*
@XStreamAlias("person")
class Associate {
@XStreamAsAttribute
@XStreamAlias('first-name')
private String firstname
@XStreamAlias('surname')
private String lastname
@XStreamOmitField
private String position
}
msg = new Associate(firstname:'Sarah',
lastname:'Connor',
position:'Protector')
Annotations.configureAliases(stream, Associate)
println stream.toXML(msg)
<person first-name="Sarah">
<surname>Connor</surname>
</person>
Agile 2007 - 72
Submission631©ASERT2007
Topics
• Introduction
• Language Basics
• Closures
• Builders
• Data Access
• Other Features
Testing with Groovy
Unit Testing
Mocks
Acceptance Testing
• Further Integration
• Grails
• More Information
Agile 2007 - 73
Submission631©ASERT2007
Built-in JUnit
• Groovy distribution includes junit (3.8.2)
• Automatically invokes text runner
• Has some useful extensions
class GroovyMultiplierJUnit3Test extends GroovyTestCase {
void testPositives() {
def testee = new GroovyMultiplier()
assertEquals "+ve multiplier error", 9, testee.triple(3)
assertEquals "+ve multiplier error", 12, testee.triple(4)
}
void testNegatives() {
def testee = new GroovyMultiplier()
assertEquals "-ve multiplier error", -12, testee.triple(-4)
}
}
..
Time: 0.092
OK (2 tests)
Agile 2007 - 74
Submission631©ASERT2007
JUnit 4.4
import org.junit.Test
import org.junit.runner.JUnitCore
import static org.junit.Assert.assertEquals
class ArithmeticTest {
@Test
void additionIsWorking() {
assertEquals 4, 2+2
}
@Test(expected=ArithmeticException)
void divideByZero() {
println 1/0
}
}
JUnitCore.main(ArithmeticTest.name)
Agile 2007 - 75
Submission631©ASERT2007
JUnit 4.4 Parameterized Tests
import org.junit.Test
import org.junit.Before
import org.junit.runner.RunWith
import org.junit.runner.JUnitCore
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.*
@RunWith(Parameterized)
class GroovyMultiplierJUnit4Test {
def testee
def param
def expected
@Parameters static data() {
return (2..4).collect{
[it, it * 3] as Integer[]
}
}
GroovyMultiplierJUnit4Test(a, b) {
param = a
expected = b
}
@Before void setUp() {
testee = new GroovyMultiplier()
}
@Test void positivesFixed() {
assert testee.triple(1) ==3
}
@Test void positivesParameterized() {
assert testee.triple(param) == expected
}
@Test void negativesParameterized() {
assert testee.triple(-param) == -expected
}
}
JUnitCore.main('GroovyMultiplierJUnit4Test')
Agile 2007 - 76
Submission631©ASERT2007
JUnit 4.4 Theories …
import org.junit.runner.*
import org.junit.experimental.theories.*
import static org.junit.Assume.assumeTrue
@RunWith(Theories)
class LanguageTheoryTest {
@DataPoint public static String java = 'Java'
@DataPoint public static String ruby = 'JRuby'
@DataPoint public static String python = 'Jython'
@DataPoint public static String javascript = 'Rhino'
@DataPoint public static String groovy = 'Groovy'
@DataPoint public static String scala = 'Scala'
@DataPoint public static String csharp = 'C#'
def jvmLanguages = [java, ruby, python, groovy, scala, javascript]
def teamSkills = [
tom: [java, groovy, ruby],
dick: [csharp, scala, java, python],
harry: [javascript, groovy, java]
]
...
Agile 2007 - 77
Submission631©ASERT2007
… JUnit 4.4 Theories
...
@Theory void everyoneKnowsJava() {
teamSkills.each { developer, skills ->
assert java in skills
}
}
@Theory void teamKnowsEachJvmLanguage(String language) {
assumeTrue language in jvmLanguages
assert teamSkills.any { mapEntry ->
language in mapEntry.value
}
}
}
JUnitCore.main(LanguageTheoryTest.name)
Agile 2007 - 78
Submission631©ASERT2007
Popper
@RunWith(Theories)
class PopperBetweenTest extends GroovyTheoryContainer {
private int test, total // for explanatory purposes only
@Theory void multiplyIsInverseOfDivide(
@Between(first = -4, last = 2) int amount,
@Between(first = -2, last = 5) int m
) {
total++
assume m != 0
assert new Dollar(amount).times(m).divideBy(m).amount == amount
test++
}
@After void dumpLog() {
println "$test tests performed out of $total combinations"
}
}
JUnitCore.main('PopperBetweenTest')
JUnit version 4.3.1
.49 tests performed out of 56 combinations
Time: 0.234
OK (1 test)
Agile 2007 - 79
Submission631©ASERT2007
Instinct
import com.googlecode.instinct.marker.annotate.BeforeSpecification as initially
import com.googlecode.instinct.marker.annotate.Specification as spec
import static com.googlecode.instinct.runner.TextContextRunner.runContexts as check_specs_for
class a_default_storer {
def storer
@initially void create_new_storer() {
storer = new Storer()
}
private check_persist_and_reverse(value, reverseValue) {
storer.put(value)
assert value == storer.get()
assert reverseValue == storer.reverse
}
@spec def should_reverse_numbers() {
check_persist_and_reverse 123.456, -123.456
}
@spec def should_reverse_strings() {
check_persist_and_reverse 'hello', 'olleh'
}
@spec def should_reverse_lists() {
check_persist_and_reverse([1, 3, 5], [5, 3, 1])
}
}
check_specs_for a_default_storer
a_default_storer
- should_reverse_lists
- should_reverse_strings
- should_reverse_numbers
Agile 2007 - 80
Submission631©ASERT2007
Built-in Mocks for Groovy…
• Handle statics, explicit constructors, etc.
import groovy.mock.interceptor.MockFor
def mocker = new MockFor(Collaborator.class) // create the Mock support
mocker.demand.one(1..2) { 1 } // demand the 'one' method one or two times, returning 1
mocker.demand.two() { 2 } // demand the 'two' method exactly once, returning 2
mocker.use { // start using the Mock
def caller = new Caller() // caller will call Collaborator
assertEquals 1, caller.collaborateOne() // will call Collaborator.one
assertEquals 1, caller.collaborateOne() // will call Collaborator.one
assertEquals 2, caller.collaborateTwo() // will call Collaborator.two
} // implicit verify for strict expectation here
…Built-in Mocks for Groovy
Agile 2007 - 81
Submission631©ASERT2007
import groovy.mock.interceptor.MockFor
class MockingTest extends GroovyTestCase {
def mock1 = new MockFor(Collaborator1)
def mock2 = new MockFor(Collaborator2)
def mock3 = new MockFor(Collaborator3)
private static final Closure DONTCARE = null
private static final Closure PASS = {}
private static final Closure FAIL = {
throw new RuntimeException() }
void testSuccess() {
check(PASS, PASS, DONTCARE)
}
void testCollaborator1Fails() {
check(FAIL, DONTCARE, PASS)
}
void testCollaborator2Fails() {
check(PASS, FAIL, PASS)
}
private check(expected1, expected2, expected3){
if (expected1) mock1.demand.method1(expected1)
if (expected2) mock2.demand.method2(expected2)
if (expected3) mock3.demand.method3(expected3)
mock1.use { mock2.use { mock3.use {
new Mocking().method()
}}}
}
}
class Mocking {
def method() {
try {
new Collaborator1().method1()
new Collaborator2().method2()
} catch (Exception e) {
new Collaborator3().method3()
}
}
}
class Collaborator1 {}
class Collaborator2 {}
class Collaborator3 {}
Agile 2007 - 82
Submission631©ASERT2007
JMock 2
import org.jmock.integration.junit4.JMock
import org.jmock.Mockery
import org.junit.Test
import org.junit.Before
import org.junit.runner.RunWith
import org.junit.runner.JUnitCore
@RunWith(JMock)
class JMock2Test {
Mockery context = new JUnit4GroovyMockery()
def mockReverser, storer
@Before void setUp() throws Exception {
mockReverser = context.mock(Reverser.class)
storer = new JavaStorer(mockReverser)
}
@Test void testStorage() {
expectReverse(123.456, -123.456)
expectReverse('hello', 'olleh')
checkReverse(123.456, -123.456)
checkReverse('hello', 'olleh')
}
def expectReverse(input, output) {
context.checking{
one(mockReverser).reverse(input); will(returnValue(output))
}
}
…
…
def checkReverse(value, reverseValue) {
storer.put(value)
assert value == storer.get()
assert reverseValue == storer.getReverse()
}
}
JUnitCore.main('JMock2Test')
class Storer {
...
def getReverse() {
return reverser.reverse(stored)
}
}
Agile 2007 - 83
Submission631©ASERT2007
WebTest testing Web Sites
def ant = new AntBuilder()
def webtest_home = System.properties.'webtest.home'
ant.taskdef(resource:'webtest.taskdef'){
classpath(){
pathelement(location:"$webtest_home/lib")
fileset(dir:"$webtest_home/lib", includes:"**/*.jar")
}
}
def config_map = [:]
['protocol','host','port','basepath','resultfile',
'resultpath', 'summary', 'saveresponse','defaultpropertytype'].each{
config_map[it] = System.properties['webtest.'+it]
}
ant.testSpec(name:'groovy: Test Groovy Scripting at creation time'){
config(config_map)
steps(){
invoke(url:'linkpage.html')
for (i in 1..10){
verifyText(description:"verify number ${i} is on pages", text:"${i}")
}
}
}
Agile 2007 - 84
Submission631©ASERT2007
WebTest testing Emails
def ant = new AntBuilder()
def webtest_home = System.properties.'webtest.home'
ant.taskdef(resource:'webtest.taskdef'){
classpath(){
pathelement(location:"$webtest_home/lib")
fileset(dir:"$webtest_home/lib", includes:"**/*.jar")
}
}
ant.testSpec(name:'Email Test'){
steps {
emailSetConfig(server:'localhost', password:'password',
username:'devteam@mycompany.org', type:'pop3')
emailStoreMessageId(subject:'/Build notification/',
property:'msg')
emailStoreHeader(property:'subject',
messageId:'#{msg}', headerName:'Subject')
groovy('''def subject = step.webtestProperties.subject
assert subject.startsWith('Build notification')''')
emailMessageContentFilter(messageId:'#{msg}')
verifyText(text:'Failed build')
}
}
SoapUI
• Tool for testing Web Services has a built-
in Groovy editor for custom steps
Agile 2007 - 85
Submission631©ASERT2007
Agile 2007 - 86
Submission631©ASERT2007
Topics
• Introduction
• Language Basics
• Closures
• Builders
• Data Access
• Other Features
• Testing with Groovy
Further Integration
• Grails
• More Information
Agile 2007 - 87
Submission631©ASERT2007
Integration With Existing Native Apps
• Scriptom allows you to script any ActiveX
or COM Windows component from within
your Groovy scripts
import org.codehaus.groovy.scriptom.ActiveXProxy
def outlook = new ActiveXProxy("Outlook.Application")
def message = outlook.CreateItem(0)
def emails = "galleon@codehaus.org;glaforge@codehaus.org"
def rec = message.Recipients.add(emails)
rec.Type = 1
message.Display(true)
Agile 2007 - 88
Submission631©ASERT2007
Integration With Existing Services
• WS or XML/RPC allow seamless
connection to existing services …
– No need to generate stubs,
– Complex types are supported
import groovy.net.soap.SoapClient
proxy = new SoapClient(
"http://www.webservicex.net/CurrencyConvertor.asmx?WSDL")
rate = proxy.ConversionRate("USD", "EUR")
println rate
SOAP Client and Server
Agile 2007 - 89
Submission631©ASERT2007
class MathService {
double add(double a, double b) {
a + b
}
double square(double c) {
c * c
}
}
import groovy.net.soap.SoapServer
def server = new SoapServer('localhost', 6789)
server.setNode('MathService')
server.start()
import groovy.net.soap.SoapClient
def math = new SoapClient('http://localhost:6789/MathServiceInterface?wsdl')
assert math.add(1.0, 2.0) == 3.0
assert math.square(3.0) == 9.0
Integration with Spring...
Agile 2007 - 90
Submission631©ASERT2007
// traditional approach using a beans xml file
import org.springframework.context.support.ClassPathXmlApplicationContext
def ctx = new ClassPathXmlApplicationContext('calcbeans.xml')
def calc = ctx.getBean('calcBean')
println calc.doAdd(3, 4) // => 7
// using BeanBuilder
def bb = new grails.spring.BeanBuilder()
bb.beans {
adder(AdderImpl)
calcBean(CalcImpl2) { adder = adder }
}
def ctx = bb.createApplicationContext()
def calc = ctx.getBean('calcBean')
println calc.doAdd(3, 4) // => 7
...Integration with Spring
Agile 2007 - 91
Submission631©ASERT2007
// using annotations
import org.springframework.stereotype.Component
@Component class AdderImpl {
def add(x, y) { x + y }
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
@Component class CalcImpl3 {
@Autowired private AdderImpl adder
def doAdd(x, y) { adder.add(x, y) }
}
import org.springframework.context.support.GenericApplicationContext
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner
def ctx = new GenericApplicationContext()
new ClassPathBeanDefinitionScanner(ctx).scan('') // scan root package for components
ctx.refresh()
def calc = ctx.getBean('calcImpl3')
println calc.doAdd(3, 4) // => 7
Groovy.Net...
Agile 2007 - 92
Submission631©ASERT2007
// To create dlls:
// ikvmc -target:library groovy-all-1.1-SNAPSHOT.jar
// Invoking groovy shell via GroovyDotNet.exe
using System;
using System.Collections.Generic;
namespace GroovyDotNet {
class MainClass {
public static void Main(string[] args) {
groovy.ui.InteractiveShell.main(args);
}
}
}
// you can invoke normal groovy
[1, 2, 3].each { println it }
// you can also invoke a .NET method
cli.System.Console.WriteLine('hello world {0}', 'from Groovy')
...Groovy.Net
Agile 2007 - 93
Submission631©ASERT2007
Agile 2007 - 94
Submission631©ASERT2007
JFreeChart
import org.jfree.chart.ChartFactory
import org.jfree.data.category.DefaultCategoryDataset
import org.jfree.chart.plot.PlotOrientation as Orientation
import groovy.swing.SwingBuilder
import javax.swing.WindowConstants as WC
def dataset = new DefaultCategoryDataset()
dataset.addValue 150, "no.1", "Jan"
dataset.addValue 210, "no.1", "Feb"
dataset.addValue 390, "no.1", "Mar"
dataset.addValue 300, "no.2", "Jan"
dataset.addValue 400, "no.2", "Feb"
dataset.addValue 200, "no.2", "Mar"
def labels = ["Bugs", "Month", "Count"]
def options = [true, true, true]
def chart = ChartFactory.createLineChart(*labels, dataset,
Orientation.VERTICAL, *options)
def swing = new SwingBuilder()
def frame = swing.frame(title:'Groovy LineChart',
defaultCloseOperation:WC.EXIT_ON_CLOSE) {
panel(id:'canvas') { rigidArea(width:400, height:400) }
}
frame.pack()
frame.show()
chart.draw(swing.canvas.graphics, swing.canvas.bounds)
Mashups
Agile 2007 - 95
Submission631©ASERT2007
Agile 2007 - 96
Submission631©ASERT2007
Topics
• Introduction
• Language Basics
• Closures
• Builders
• Data Access
• Other Features
• Testing with Groovy
• Further Integration
Grails
• More Information
Wikipedia says:
• Grails has been developed with a number of
goals in mind:
– Provide a high-productivity web framework for the Java platform
– Offer a consistent framework that takes away confusion and is
easy to learn
– Offer documentation for those parts of the framework that matter
for its users
– Provide what users expect in areas that are often complex and
inconsistent:
• Powerful and consistent persistence framework
• Powerful and easy to use view templates using GSP (Groovy Server Pages)
• Dynamic tag libraries to easily create web page components
• Good Ajax support that is easy to extend and customize
– Provide sample applications that demonstrate the power of the
framework
– Provide a complete development mode, including web server
and automatic reload of resources
Agile 2007 - 97
Submission631©ASERT2007
Goals
• Grails has three properties that increase
developer productivity significantly when
compared to traditional Java web frameworks:
– No XML configuration
– Ready-to-use development environment
– Functionality available through mixins
Agile 2007 - 98
Submission631©ASERT2007
Web framework
• Controllers
• Views
Agile 2007 - 99
Submission631©ASERT2007
class BookController {
def list = {
[ books: Book.findAll() ]
}
}
<html>
<head>
<title>Our books</title>
</head>
<body>
<ul>
<g:each it="books">
<li>${it.title} (${it.author.name})</li>
</g:each>
</ul>
</body>
</html>
grails create-controller
Persistence
• Model
• Methods
Agile 2007 - 100
Submission631©ASERT2007
class Book {
Long id
Long version
String title
Person author
}
def book = new Book(title:"The Da Vinci Code", author:Author.findByName("Dan Brown"))
book.save()
grails create-domain-class
def book = Book.find("from Book b where b.title = ?", [ 'The Da Vinci Code' ])
def books = Book.findAll()
def book = Book.findByTitleLike("%Da Vinci%")
def book = Book.findWhere(title:"The Da Vinci Code")
Agile 2007 - 101
Submission631©ASERT2007
Project Structure
%PROJECT_HOME%
+ grails-app
+ conf ---> location of configuration artifacts like data sources
+ controllers ---> location of controller artifacts
+ domain ---> location of domain classes
+ i18n ---> location of message bundles for i18n
+ services ---> location of services
+ taglib ---> location of tag libraries
+ util ---> location of special utility classes (e.g., codecs, etc.)
+ views ---> location of views
+ layouts ---> location of layouts
+ hibernate ---> optional hibernate config
+ lib
+ spring ---> optional spring config
+ src
+ groovy ---> optional; location for Groovy source files
(of types other than those in grails-app/*)
+ java ---> optional; location for Java source files
+ war
+ WEB-INF
Agile 2007 - 102
Submission631©ASERT2007
Topics
• Introduction
• Language Basics
• Closures
• Builders
• Data Access
• Other Features
• Testing with Groovy
• Further Integration
• Grails
More Information
More Information: on the web
• Web sites
– http://groovy.codehaus.org
– http://grails.codehaus.org
– http://pleac.sourceforge.net/pleac_groovy (many examples)
– http://www.asert.com.au/training/java/GV110.htm (workshop)
• Mailing list for users
– user@groovy.codehaus.org
• Information portals
– http://www.aboutgroovy.org
– http://www.groovyblogs.org
• Documentation (600+ pages)
– Getting Started Guide, User Guide, Developer Guide, Testing
Guide, Cookbook Examples, Advanced Usage Guide
• Books
– Several to choose from ...
Agile 2007 - 103
Submission631©ASERT2007
More Information: Groovy in Action
Agile 2007 - 104
Submission631©ASERT2007

Weitere ähnliche Inhalte

Was ist angesagt?

POLITEKNIK MALAYSIA
POLITEKNIK MALAYSIAPOLITEKNIK MALAYSIA
POLITEKNIK MALAYSIAAiman Hud
 
CS101- Introduction to Computing- Lecture 26
CS101- Introduction to Computing- Lecture 26CS101- Introduction to Computing- Lecture 26
CS101- Introduction to Computing- Lecture 26Bilal Ahmed
 
Flink Forward Berlin 2017: David Rodriguez - The Approximate Filter, Join, an...
Flink Forward Berlin 2017: David Rodriguez - The Approximate Filter, Join, an...Flink Forward Berlin 2017: David Rodriguez - The Approximate Filter, Join, an...
Flink Forward Berlin 2017: David Rodriguez - The Approximate Filter, Join, an...Flink Forward
 
Kotlin, Spek and tests
Kotlin, Spek and testsKotlin, Spek and tests
Kotlin, Spek and testsintive
 
Python tutorialfeb152012
Python tutorialfeb152012Python tutorialfeb152012
Python tutorialfeb152012Shani729
 
Indexing and Query Optimizer (Aaron Staple)
Indexing and Query Optimizer (Aaron Staple)Indexing and Query Optimizer (Aaron Staple)
Indexing and Query Optimizer (Aaron Staple)MongoSF
 
Introduction to Erlang
Introduction to ErlangIntroduction to Erlang
Introduction to ErlangGabriele Lana
 
What's new in Doctrine
What's new in DoctrineWhat's new in Doctrine
What's new in DoctrineJonathan Wage
 
Extracting Executable Transformations from Distilled Code Changes
Extracting Executable Transformations from Distilled Code ChangesExtracting Executable Transformations from Distilled Code Changes
Extracting Executable Transformations from Distilled Code Changesstevensreinout
 
Python dictionary : past, present, future
Python dictionary: past, present, futurePython dictionary: past, present, future
Python dictionary : past, present, futuredelimitry
 
Python PCEP Tuples and Dictionaries
Python PCEP Tuples and DictionariesPython PCEP Tuples and Dictionaries
Python PCEP Tuples and DictionariesIHTMINSTITUTE
 

Was ist angesagt? (20)

Scala introduction
Scala introductionScala introduction
Scala introduction
 
Oop lecture7
Oop lecture7Oop lecture7
Oop lecture7
 
POLITEKNIK MALAYSIA
POLITEKNIK MALAYSIAPOLITEKNIK MALAYSIA
POLITEKNIK MALAYSIA
 
CS101- Introduction to Computing- Lecture 26
CS101- Introduction to Computing- Lecture 26CS101- Introduction to Computing- Lecture 26
CS101- Introduction to Computing- Lecture 26
 
Trie Data Structure
Trie Data StructureTrie Data Structure
Trie Data Structure
 
Into Clojure
Into ClojureInto Clojure
Into Clojure
 
Plc (1)
Plc (1)Plc (1)
Plc (1)
 
Trie Data Structure
Trie Data StructureTrie Data Structure
Trie Data Structure
 
Plc (1)
Plc (1)Plc (1)
Plc (1)
 
Flink Forward Berlin 2017: David Rodriguez - The Approximate Filter, Join, an...
Flink Forward Berlin 2017: David Rodriguez - The Approximate Filter, Join, an...Flink Forward Berlin 2017: David Rodriguez - The Approximate Filter, Join, an...
Flink Forward Berlin 2017: David Rodriguez - The Approximate Filter, Join, an...
 
Kotlin, Spek and tests
Kotlin, Spek and testsKotlin, Spek and tests
Kotlin, Spek and tests
 
Python tutorialfeb152012
Python tutorialfeb152012Python tutorialfeb152012
Python tutorialfeb152012
 
Chap1 array
Chap1 arrayChap1 array
Chap1 array
 
Indexing and Query Optimizer (Aaron Staple)
Indexing and Query Optimizer (Aaron Staple)Indexing and Query Optimizer (Aaron Staple)
Indexing and Query Optimizer (Aaron Staple)
 
07slide
07slide07slide
07slide
 
Introduction to Erlang
Introduction to ErlangIntroduction to Erlang
Introduction to Erlang
 
What's new in Doctrine
What's new in DoctrineWhat's new in Doctrine
What's new in Doctrine
 
Extracting Executable Transformations from Distilled Code Changes
Extracting Executable Transformations from Distilled Code ChangesExtracting Executable Transformations from Distilled Code Changes
Extracting Executable Transformations from Distilled Code Changes
 
Python dictionary : past, present, future
Python dictionary: past, present, futurePython dictionary: past, present, future
Python dictionary : past, present, future
 
Python PCEP Tuples and Dictionaries
Python PCEP Tuples and DictionariesPython PCEP Tuples and Dictionaries
Python PCEP Tuples and Dictionaries
 

Ähnlich wie Groovy

groovy rules
groovy rulesgroovy rules
groovy rulesPaul King
 
awesome groovy
awesome groovyawesome groovy
awesome groovyPaul King
 
Mixing functional and object oriented approaches to programming in C#
Mixing functional and object oriented approaches to programming in C#Mixing functional and object oriented approaches to programming in C#
Mixing functional and object oriented approaches to programming in C#Mark Needham
 
Scala - en bedre og mere effektiv Java?
Scala - en bedre og mere effektiv Java?Scala - en bedre og mere effektiv Java?
Scala - en bedre og mere effektiv Java?Jesper Kamstrup Linnet
 
07. Java Array, Set and Maps
07.  Java Array, Set and Maps07.  Java Array, Set and Maps
07. Java Array, Set and MapsIntro C# Book
 
4java Basic Syntax
4java Basic Syntax4java Basic Syntax
4java Basic SyntaxAdil Jafri
 
Java → kotlin: Tests Made Simple
Java → kotlin: Tests Made SimpleJava → kotlin: Tests Made Simple
Java → kotlin: Tests Made Simpleleonsabr
 
Professional-grade software design
Professional-grade software designProfessional-grade software design
Professional-grade software designBrian Fenton
 
Mixing functional programming approaches in an object oriented language
Mixing functional programming approaches in an object oriented languageMixing functional programming approaches in an object oriented language
Mixing functional programming approaches in an object oriented languageMark Needham
 
LECTURE 2 MORE TYPES, METHODS, CONDITIONALS.pdf
LECTURE 2 MORE TYPES, METHODS, CONDITIONALS.pdfLECTURE 2 MORE TYPES, METHODS, CONDITIONALS.pdf
LECTURE 2 MORE TYPES, METHODS, CONDITIONALS.pdfShashikantSathe3
 
Softshake 2013: 10 reasons why java developers are jealous of Scala developers
Softshake 2013: 10 reasons why java developers are jealous of Scala developersSoftshake 2013: 10 reasons why java developers are jealous of Scala developers
Softshake 2013: 10 reasons why java developers are jealous of Scala developersMatthew Farwell
 
Pi j3.4 data-structures
Pi j3.4 data-structuresPi j3.4 data-structures
Pi j3.4 data-structuresmcollison
 

Ähnlich wie Groovy (20)

groovy rules
groovy rulesgroovy rules
groovy rules
 
awesome groovy
awesome groovyawesome groovy
awesome groovy
 
JDK 8
JDK 8JDK 8
JDK 8
 
What is new in Java 8
What is new in Java 8What is new in Java 8
What is new in Java 8
 
Mixing functional and object oriented approaches to programming in C#
Mixing functional and object oriented approaches to programming in C#Mixing functional and object oriented approaches to programming in C#
Mixing functional and object oriented approaches to programming in C#
 
Scala - en bedre og mere effektiv Java?
Scala - en bedre og mere effektiv Java?Scala - en bedre og mere effektiv Java?
Scala - en bedre og mere effektiv Java?
 
07. Java Array, Set and Maps
07.  Java Array, Set and Maps07.  Java Array, Set and Maps
07. Java Array, Set and Maps
 
4java Basic Syntax
4java Basic Syntax4java Basic Syntax
4java Basic Syntax
 
Java → kotlin: Tests Made Simple
Java → kotlin: Tests Made SimpleJava → kotlin: Tests Made Simple
Java → kotlin: Tests Made Simple
 
FNT 2015 PDIS CodeEU - Zanimljiva informatika - 02 Djordje Pavlovic - Live_ch...
FNT 2015 PDIS CodeEU - Zanimljiva informatika - 02 Djordje Pavlovic - Live_ch...FNT 2015 PDIS CodeEU - Zanimljiva informatika - 02 Djordje Pavlovic - Live_ch...
FNT 2015 PDIS CodeEU - Zanimljiva informatika - 02 Djordje Pavlovic - Live_ch...
 
Scala
ScalaScala
Scala
 
Einführung in TypeScript
Einführung in TypeScriptEinführung in TypeScript
Einführung in TypeScript
 
Professional-grade software design
Professional-grade software designProfessional-grade software design
Professional-grade software design
 
Mixing functional programming approaches in an object oriented language
Mixing functional programming approaches in an object oriented languageMixing functional programming approaches in an object oriented language
Mixing functional programming approaches in an object oriented language
 
Alternate JVM Languages
Alternate JVM LanguagesAlternate JVM Languages
Alternate JVM Languages
 
LECTURE 2 MORE TYPES, METHODS, CONDITIONALS.pdf
LECTURE 2 MORE TYPES, METHODS, CONDITIONALS.pdfLECTURE 2 MORE TYPES, METHODS, CONDITIONALS.pdf
LECTURE 2 MORE TYPES, METHODS, CONDITIONALS.pdf
 
Java arrays
Java   arraysJava   arrays
Java arrays
 
Basic data-structures-v.1.1
Basic data-structures-v.1.1Basic data-structures-v.1.1
Basic data-structures-v.1.1
 
Softshake 2013: 10 reasons why java developers are jealous of Scala developers
Softshake 2013: 10 reasons why java developers are jealous of Scala developersSoftshake 2013: 10 reasons why java developers are jealous of Scala developers
Softshake 2013: 10 reasons why java developers are jealous of Scala developers
 
Pi j3.4 data-structures
Pi j3.4 data-structuresPi j3.4 data-structures
Pi j3.4 data-structures
 

Kürzlich hochgeladen

VIRUSES structure and classification ppt by Dr.Prince C P
VIRUSES structure and classification ppt by Dr.Prince C PVIRUSES structure and classification ppt by Dr.Prince C P
VIRUSES structure and classification ppt by Dr.Prince C PPRINCE C P
 
Stunning ➥8448380779▻ Call Girls In Panchshil Enclave Delhi NCR
Stunning ➥8448380779▻ Call Girls In Panchshil Enclave Delhi NCRStunning ➥8448380779▻ Call Girls In Panchshil Enclave Delhi NCR
Stunning ➥8448380779▻ Call Girls In Panchshil Enclave Delhi NCRDelhi Call girls
 
Nanoparticles synthesis and characterization​ ​
Nanoparticles synthesis and characterization​  ​Nanoparticles synthesis and characterization​  ​
Nanoparticles synthesis and characterization​ ​kaibalyasahoo82800
 
PossibleEoarcheanRecordsoftheGeomagneticFieldPreservedintheIsuaSupracrustalBe...
PossibleEoarcheanRecordsoftheGeomagneticFieldPreservedintheIsuaSupracrustalBe...PossibleEoarcheanRecordsoftheGeomagneticFieldPreservedintheIsuaSupracrustalBe...
PossibleEoarcheanRecordsoftheGeomagneticFieldPreservedintheIsuaSupracrustalBe...Sérgio Sacani
 
Animal Communication- Auditory and Visual.pptx
Animal Communication- Auditory and Visual.pptxAnimal Communication- Auditory and Visual.pptx
Animal Communication- Auditory and Visual.pptxUmerFayaz5
 
Broad bean, Lima Bean, Jack bean, Ullucus.pptx
Broad bean, Lima Bean, Jack bean, Ullucus.pptxBroad bean, Lima Bean, Jack bean, Ullucus.pptx
Broad bean, Lima Bean, Jack bean, Ullucus.pptxjana861314
 
SOLUBLE PATTERN RECOGNITION RECEPTORS.pptx
SOLUBLE PATTERN RECOGNITION RECEPTORS.pptxSOLUBLE PATTERN RECOGNITION RECEPTORS.pptx
SOLUBLE PATTERN RECOGNITION RECEPTORS.pptxkessiyaTpeter
 
Recombination DNA Technology (Nucleic Acid Hybridization )
Recombination DNA Technology (Nucleic Acid Hybridization )Recombination DNA Technology (Nucleic Acid Hybridization )
Recombination DNA Technology (Nucleic Acid Hybridization )aarthirajkumar25
 
Traditional Agroforestry System in India- Shifting Cultivation, Taungya, Home...
Traditional Agroforestry System in India- Shifting Cultivation, Taungya, Home...Traditional Agroforestry System in India- Shifting Cultivation, Taungya, Home...
Traditional Agroforestry System in India- Shifting Cultivation, Taungya, Home...jana861314
 
Zoology 4th semester series (krishna).pdf
Zoology 4th semester series (krishna).pdfZoology 4th semester series (krishna).pdf
Zoology 4th semester series (krishna).pdfSumit Kumar yadav
 
Chemistry 4th semester series (krishna).pdf
Chemistry 4th semester series (krishna).pdfChemistry 4th semester series (krishna).pdf
Chemistry 4th semester series (krishna).pdfSumit Kumar yadav
 
Hubble Asteroid Hunter III. Physical properties of newly found asteroids
Hubble Asteroid Hunter III. Physical properties of newly found asteroidsHubble Asteroid Hunter III. Physical properties of newly found asteroids
Hubble Asteroid Hunter III. Physical properties of newly found asteroidsSérgio Sacani
 
GFP in rDNA Technology (Biotechnology).pptx
GFP in rDNA Technology (Biotechnology).pptxGFP in rDNA Technology (Biotechnology).pptx
GFP in rDNA Technology (Biotechnology).pptxAleenaTreesaSaji
 
Raman spectroscopy.pptx M Pharm, M Sc, Advanced Spectral Analysis
Raman spectroscopy.pptx M Pharm, M Sc, Advanced Spectral AnalysisRaman spectroscopy.pptx M Pharm, M Sc, Advanced Spectral Analysis
Raman spectroscopy.pptx M Pharm, M Sc, Advanced Spectral AnalysisDiwakar Mishra
 
GBSN - Biochemistry (Unit 1)
GBSN - Biochemistry (Unit 1)GBSN - Biochemistry (Unit 1)
GBSN - Biochemistry (Unit 1)Areesha Ahmad
 
Hire 💕 9907093804 Hooghly Call Girls Service Call Girls Agency
Hire 💕 9907093804 Hooghly Call Girls Service Call Girls AgencyHire 💕 9907093804 Hooghly Call Girls Service Call Girls Agency
Hire 💕 9907093804 Hooghly Call Girls Service Call Girls AgencySheetal Arora
 

Kürzlich hochgeladen (20)

CELL -Structural and Functional unit of life.pdf
CELL -Structural and Functional unit of life.pdfCELL -Structural and Functional unit of life.pdf
CELL -Structural and Functional unit of life.pdf
 
VIRUSES structure and classification ppt by Dr.Prince C P
VIRUSES structure and classification ppt by Dr.Prince C PVIRUSES structure and classification ppt by Dr.Prince C P
VIRUSES structure and classification ppt by Dr.Prince C P
 
The Philosophy of Science
The Philosophy of ScienceThe Philosophy of Science
The Philosophy of Science
 
Stunning ➥8448380779▻ Call Girls In Panchshil Enclave Delhi NCR
Stunning ➥8448380779▻ Call Girls In Panchshil Enclave Delhi NCRStunning ➥8448380779▻ Call Girls In Panchshil Enclave Delhi NCR
Stunning ➥8448380779▻ Call Girls In Panchshil Enclave Delhi NCR
 
Nanoparticles synthesis and characterization​ ​
Nanoparticles synthesis and characterization​  ​Nanoparticles synthesis and characterization​  ​
Nanoparticles synthesis and characterization​ ​
 
PossibleEoarcheanRecordsoftheGeomagneticFieldPreservedintheIsuaSupracrustalBe...
PossibleEoarcheanRecordsoftheGeomagneticFieldPreservedintheIsuaSupracrustalBe...PossibleEoarcheanRecordsoftheGeomagneticFieldPreservedintheIsuaSupracrustalBe...
PossibleEoarcheanRecordsoftheGeomagneticFieldPreservedintheIsuaSupracrustalBe...
 
Animal Communication- Auditory and Visual.pptx
Animal Communication- Auditory and Visual.pptxAnimal Communication- Auditory and Visual.pptx
Animal Communication- Auditory and Visual.pptx
 
Broad bean, Lima Bean, Jack bean, Ullucus.pptx
Broad bean, Lima Bean, Jack bean, Ullucus.pptxBroad bean, Lima Bean, Jack bean, Ullucus.pptx
Broad bean, Lima Bean, Jack bean, Ullucus.pptx
 
SOLUBLE PATTERN RECOGNITION RECEPTORS.pptx
SOLUBLE PATTERN RECOGNITION RECEPTORS.pptxSOLUBLE PATTERN RECOGNITION RECEPTORS.pptx
SOLUBLE PATTERN RECOGNITION RECEPTORS.pptx
 
Recombination DNA Technology (Nucleic Acid Hybridization )
Recombination DNA Technology (Nucleic Acid Hybridization )Recombination DNA Technology (Nucleic Acid Hybridization )
Recombination DNA Technology (Nucleic Acid Hybridization )
 
Traditional Agroforestry System in India- Shifting Cultivation, Taungya, Home...
Traditional Agroforestry System in India- Shifting Cultivation, Taungya, Home...Traditional Agroforestry System in India- Shifting Cultivation, Taungya, Home...
Traditional Agroforestry System in India- Shifting Cultivation, Taungya, Home...
 
Zoology 4th semester series (krishna).pdf
Zoology 4th semester series (krishna).pdfZoology 4th semester series (krishna).pdf
Zoology 4th semester series (krishna).pdf
 
Chemistry 4th semester series (krishna).pdf
Chemistry 4th semester series (krishna).pdfChemistry 4th semester series (krishna).pdf
Chemistry 4th semester series (krishna).pdf
 
Engler and Prantl system of classification in plant taxonomy
Engler and Prantl system of classification in plant taxonomyEngler and Prantl system of classification in plant taxonomy
Engler and Prantl system of classification in plant taxonomy
 
Hubble Asteroid Hunter III. Physical properties of newly found asteroids
Hubble Asteroid Hunter III. Physical properties of newly found asteroidsHubble Asteroid Hunter III. Physical properties of newly found asteroids
Hubble Asteroid Hunter III. Physical properties of newly found asteroids
 
GFP in rDNA Technology (Biotechnology).pptx
GFP in rDNA Technology (Biotechnology).pptxGFP in rDNA Technology (Biotechnology).pptx
GFP in rDNA Technology (Biotechnology).pptx
 
Raman spectroscopy.pptx M Pharm, M Sc, Advanced Spectral Analysis
Raman spectroscopy.pptx M Pharm, M Sc, Advanced Spectral AnalysisRaman spectroscopy.pptx M Pharm, M Sc, Advanced Spectral Analysis
Raman spectroscopy.pptx M Pharm, M Sc, Advanced Spectral Analysis
 
GBSN - Biochemistry (Unit 1)
GBSN - Biochemistry (Unit 1)GBSN - Biochemistry (Unit 1)
GBSN - Biochemistry (Unit 1)
 
9953056974 Young Call Girls In Mahavir enclave Indian Quality Escort service
9953056974 Young Call Girls In Mahavir enclave Indian Quality Escort service9953056974 Young Call Girls In Mahavir enclave Indian Quality Escort service
9953056974 Young Call Girls In Mahavir enclave Indian Quality Escort service
 
Hire 💕 9907093804 Hooghly Call Girls Service Call Girls Agency
Hire 💕 9907093804 Hooghly Call Girls Service Call Girls AgencyHire 💕 9907093804 Hooghly Call Girls Service Call Girls Agency
Hire 💕 9907093804 Hooghly Call Girls Service Call Girls Agency
 

Groovy

  • 1. Submission631©ASERT2007 Agile 2007 - 1 Groovy Tutorial Dr Paul King ASERT, Australia
  • 2. Agile 2007 - 2 Submission631©ASERT2007 Topics Introduction • Language Basics • Closures • Builders • Data Access • Other Features • Testing with Groovy • Further Integration • Grails • More Information
  • 3. Agile 2007 - 3 Submission631©ASERT2007 What is Groovy? • “Groovy is like a super version of Java. It can leverage Java's enterprise capabilities but also has cool productivity features like closures, DSL support, builders and dynamic typing.” Groovy = Java – boiler plate code + optional dynamic typing + closures + domain specific languages + builders + metaprogramming
  • 4. Agile 2007 - 4 Submission631©ASERT2007 Groovy Goodies Overview • Fully object oriented • Closures: reusable and assignable pieces of code • Operators can be overloaded • Multimethods • Literal declaration for lists (arrays), maps, ranges and regular expressions • GPath: efficient object navigation • GroovyBeans • grep and switch • Templates, builder, swing, Ant, markup, XML, SQL, XML-RPC, Scriptom, Grails, tests, Mocks
  • 5. Agile 2007 - 5 Submission631©ASERT2007 Growing Acceptance • A slow and steady start but now gaining in momentum; growing in maturity & mindshare Groovy Recipes Pragmatic Bookshelf
  • 6. Agile 2007 - 6 Submission631©ASERT2007 The Landscape of JVM Languages Java bytecode calls for static types Dynamic features call for dynamic types optional static types The terms “Java Virtual Machine” and “JVM” mean a Virtual Machine for the Java™ platform.
  • 7. Agile 2007 - 7 Submission631©ASERT2007 Groovy Starter System.out.println("Hello, World!"); // optional semicolon, println 'Hello, World!' // System.out, brackets, // main() method def name = 'Guillaume' // dynamic typing println "$name, I'll get the car." // GString String longer = """${name}, the car is in the next row.""" // multi-line string // with static typing assert 0.5 == 1/2 // BigDecimal equals() def printSize(obj) { // optional duck typing print obj?.size() // safe dereferencing } def animals = ['ant', 'bee', 'cat'] // native list syntax assert animals.every { pet -> // closure support pet < 'dog' // overloading }
  • 8. A Better Java... Agile 2007 - 8 Submission631©ASERT2007 import java.util.List; import java.util.ArrayList; class Erase { private List filterLongerThan(List strings, int length) { List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { String s = (String) strings.get(i); if (s.length() <= length) { result.add(s); } } return result; } public static void main(String[] args) { List names = new ArrayList(); names.add("Ted"); names.add("Fred"); names.add("Jed"); names.add("Ned"); System.out.println(names); Erase e = new Erase(); List shortNames = e.filterLongerThan(names, 3); System.out.println(shortNames.size()); for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } } This code is valid Java and valid Groovy Based on an example by Jim Weirich & Ted Leung
  • 9. ...A Better Java... Agile 2007 - 9 Submission631©ASERT2007 import java.util.List; import java.util.ArrayList; class Erase { private List filterLongerThan(List strings, int length) { List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { String s = (String) strings.get(i); if (s.length() <= length) { result.add(s); } } return result; } public static void main(String[] args) { List names = new ArrayList(); names.add("Ted"); names.add("Fred"); names.add("Jed"); names.add("Ned"); System.out.println(names); Erase e = new Erase(); List shortNames = e.filterLongerThan(names, 3); System.out.println(shortNames.size()); for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } } Do the semicolons add anything? And shouldn’t we us more modern list notation? Why not import common libraries?
  • 10. ...A Better Java... Agile 2007 - 10 Submission631©ASERT2007 class Erase { private List filterLongerThan(List strings, int length) { List result = new ArrayList() for (String s in strings) { if (s.length() <= length) { result.add(s) } } return result } public static void main(String[] args) { List names = new ArrayList() names.add("Ted"); names.add("Fred") names.add("Jed"); names.add("Ned") System.out.println(names) Erase e = new Erase() List shortNames = e.filterLongerThan(names, 3) System.out.println(shortNames.size()) for (String s in shortNames) { System.out.println(s) } } }
  • 11. ...A Better Java... Agile 2007 - 11 Submission631©ASERT2007 class Erase { private List filterLongerThan(List strings, int length) { List result = new ArrayList() for (String s in strings) { if (s.length() <= length) { result.add(s) } } return result } public static void main(String[] args) { List names = new ArrayList() names.add("Ted"); names.add("Fred") names.add("Jed"); names.add("Ned") System.out.println(names) Erase e = new Erase() List shortNames = e.filterLongerThan(names, 3) System.out.println(shortNames.size()) for (String s in shortNames) { System.out.println(s) } } } Do we need the static types? Must we always have a main method and class definition? How about improved consistency?
  • 12. ...A Better Java... Agile 2007 - 12 Submission631©ASERT2007 def filterLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) } } return result } names = new ArrayList() names.add("Ted") names.add("Fred") names.add("Jed") names.add("Ned") System.out.println(names) shortNames = filterLongerThan(names, 3) System.out.println(shortNames.size()) for (s in shortNames) { System.out.println(s) }
  • 13. ...A Better Java... Agile 2007 - 13 Submission631©ASERT2007 def filterLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) } } return result } names = new ArrayList() names.add("Ted") names.add("Fred") names.add("Jed") names.add("Ned") System.out.println(names) shortNames = filterLongerThan(names, 3) System.out.println(shortNames.size()) for (s in shortNames) { System.out.println(s) } Shouldn’t we have special notation for lists? And special facilities for list processing?
  • 14. ...A Better Java... Agile 2007 - 14 Submission631©ASERT2007 def filterLongerThan(strings, length) { return strings.findAll{ it.size() <= length } } names = ["Ted", "Fred", "Jed", "Ned"] System.out.println(names) shortNames = filterLongerThan(names, 3) System.out.println(shortNames.size()) shortNames.each{ System.out.println(s) }
  • 15. ...A Better Java... Agile 2007 - 15 Submission631©ASERT2007 def filterLongerThan(strings, length) { return strings.findAll{ it.size() <= length } } names = ["Ted", "Fred", "Jed", "Ned"] System.out.println(names) shortNames = filterLongerThan(names, 3) System.out.println(shortNames.size()) shortNames.each{ System.out.println(s) } Is the method now needed? Easier ways to use common methods? Are brackets required here?
  • 16. ...A Better Java... Agile 2007 - 16 Submission631©ASERT2007 names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it } ["Ted", "Fred", "Jed", "Ned"] 3 Ted Jed Ned
  • 17. ...A Better Java Agile 2007 - 17 Submission631©ASERT2007 names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it } import java.util.List; import java.util.ArrayList; class Erase { private List filterLongerThan(List strings, int length) { List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { String s = (String) strings.get(i); if (s.length() <= length) { result.add(s); } } return result; } public static void main(String[] args) { List names = new ArrayList(); names.add("Ted"); names.add("Fred"); names.add("Jed"); names.add("Ned"); System.out.println(names); Erase e = new Erase(); List shortNames = e.filterLongerThan(names, 3); System.out.println(shortNames.size()); for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } }
  • 18. ... public void setModel(String model) { this.model = model; } public int getFrame() { return frame; } public String getSerialNo() { return serialNo; } public void setSerialNo(String serialNo) { this.serialNo = serialNo; } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public BigDecimal getCost() { return cost; } public void setCost(BigDecimal cost) { this.cost = cost.setScale(3, BigDecimal.ROUND_HALF_UP); } } Better JavaBeans... Agile 2007 - 18 Submission631©ASERT2007 import java.math.BigDecimal; public class BikeJavaBean { private String manufacturer; private String model; private int frame; private String serialNo; private double weight; private String status; private BigDecimal cost; public BikeJavaBean(String manufacturer, String model, int frame, String serialNo, double weight, String status) { this.manufacturer = manufacturer; this.model = model; this.frame = frame; this.serialNo = serialNo; this.weight = weight; this.status = status; } public String toString() { return "Bike:‚ + ‚n manufacturer -- " + manufacturer + "n model -- " + model + "n frame -- " + frame + "n serialNo -- " + serialNo + "n"; } public String getManufacturer() { return manufacturer; } public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; } public String getModel() { return model; } ... Modified example from: http://www.ibm.com/developerworks/library/j-pg09196.html
  • 19. ... public void setModel(String model) { this.model = model; } public int getFrame() { return frame; } public String getSerialNo() { return serialNo; } public void setSerialNo(String serialNo) { this.serialNo = serialNo; } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public BigDecimal getCost() { return cost; } public void setCost(BigDecimal cost) { this.cost = cost.setScale(3, BigDecimal.ROUND_HALF_UP); } } ...Better JavaBeans... Agile 2007 - 19 Submission631©ASERT2007 import java.math.BigDecimal; public class BikeJavaBean { private String manufacturer; private String model; private int frame; private String serialNo; private double weight; private String status; private BigDecimal cost; public BikeJavaBean(String manufacturer, String model, int frame, String serialNo, double weight, String status) { this.manufacturer = manufacturer; this.model = model; this.frame = frame; this.serialNo = serialNo; this.weight = weight; this.status = status; } public String toString() { return "Bike:‚ + ‚n manufacturer -- " + manufacturer + "n model -- " + model + "n frame -- " + frame + "n serialNo -- " + serialNo + "n"; } public String getManufacturer() { return manufacturer; } public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; } public String getModel() { return model; } ... Auto getters? Auto setters? Auto construction?
  • 20. ... Better JavaBeans Agile 2007 - 20 Submission631©ASERT2007 class BikeGroovyBean { String manufacturer, model, serialNo, status final Integer frame Double weight BigDecimal cost public void setCost(BigDecimal newCost) { cost = newCost.setScale(3, BigDecimal.ROUND_HALF_UP) } public String toString() { return """Bike: manufacturer -- $manufacturer model -- $model frame -- $frame serialNo -- $serialNo """ } }
  • 21. Agile 2007 - 21 Submission631©ASERT2007 Topics • Introduction Language Basics • Closures • Builders • Data Access • Other Features • Testing with Groovy • Further Integration • Grails • More Information
  • 22. Agile 2007 - 22 Submission631©ASERT2007 Strings… • Several forms – Single quotes for simple strings – Double quotes for GStrings which support variable expansion – Slashy strings behave like GStrings but preserve backslashes (great for regex and directory names) – Multi-line versions // normal strings def firstname = 'Kate' def surname= "Bush" assert firstname * 2 == 'KateKate' // GString def fullname = "$firstname $surname" assert fullname == 'Kate Bush' assert fullname - firstname == ' Bush' assert fullname.padLeft(10) == ' Kate Bush' // indexing (including ranges) assert fullname[0..3] == firstname assert fullname[-4..-1] == surname assert fullname[5, 3..1] == 'Beta'
  • 23. …Strings… Agile 2007 - 23 Submission631©ASERT2007 // slashy string: (almost) no escaping def path = /C:WindowsSystem32/ def plain = 'nrtbf$' assert plain.size() == 7 def slashy = /nrtbf$/ assert slashy.size() == 14 // late binding trick with closures fullname = "${-> firstname} $surname" assert fullname == 'Kate Bush' firstname = 'George' surname = 'Clooney' assert fullname == 'George Bush' // Multi-line strings def twister = ''' She sells, sea shells By the sea shore''' def lines = twister.split('n') assert lines.size() == 2 def address = """ $fullname 123 First Ave New York """.trim() def lines = address.split('n') assert lines.size() == 3 println """ ---------------------- | $fullname | | 123 First Ave | | New York | ---------------------- """
  • 24. …Strings Agile 2007 - 24 Submission631©ASERT2007 // more substrings string = 'hippopotamus' assert string - 'hippo' - 'mus' + 'to' == 'potato' assert string.replace('ppopotam','bisc') == 'hibiscus' // processing characters assert 'apple'.toList() == ['a', 'p', 'p', 'l', 'e'] //also: 'apple' as String[], 'apple'.split(''), 'apple'.each{} string = "an apple a day" assert string.toList().unique().sort().join() == ' adelnpy' // reversing chars/words assert 'string'.reverse() == 'gnirts' string = 'Yoda said, "can you see this?"' revwords = string.split(' ').toList().reverse().join(' ') assert revwords == 'this?" see you "can said, Yoda' words = ['bob', 'alpha', 'rotator', 'omega', 'reviver'] bigPalindromes = words.findAll{w -> w == w.reverse() && w.size() > 5} assert bigPalindromes == ['rotator', 'reviver']
  • 25. Numbers... • Java Approach – Supports primitive types and object types – Has wrapper classes to allow conversion – Java 5+ has autoboxing to hide difference • Groovy Approach – Treats everything as an object at the language level • And does appropriate autoboxing under the covers when integrating with Java – BigDecimal used as the default type for non-Integers – Operator overloading applies to all common operations on numbers, e.g. 3 * 4 is the same as 3.multiply(4) • You can use these operators for your own types, e.g. Person * 3 calls the multiple method on your person object Agile 2007 - 25 Submission631©ASERT2007
  • 26. ...Numbers Agile 2007 - 26 Submission631©ASERT2007 def x = 3 def y = 4 assert x + y == 7 assert x.plus(y) == 7 assert x instanceof Integer assert 0.5 == 1/2 // uses BigDecimal arithmetic as default def a = 2 / 3 // 0.6666666666 def b = a.setScale(3, BigDecimal.ROUND_HALF_UP) assert b.toString() == '0.667' assert 4 + 3 == 7 // 4.plus(3) assert 4 - 3 == 1 // 4.minus(3) assert 4 * 3 == 12 // 4.multiply(12) assert 4 % 3 == 1 // 4.mod(3) assert 4 ** 3 == 64 // 4.power(3) assert 4 / 3 == 1.3333333333 // 4.div(3) assert 4.intdiv(3) == 1 // normal integer division assert !(4 == 3) // !(4.equals(3)) assert 4 != 3 // ! 4.equals(3) assert !(4 < 3) // 4.compareTo(3) < 0 assert !(4 <= 3) // 4.compareTo(3) <= 0 assert 4 > 3 // 4.compareTo(3) > 0 assert 4 >= 3 // 4.compareTo(3) >= 0 assert 4 <=> 3 == 1 // 4.compareTo(3)
  • 27. Dates... • Mostly same support as Java • Use java.util.Date or java.util.Calendar – Or java.sql.Date etc. – Can use static imports to help • Or 3rd party package like Joda Time • Does have special support for changing times: date2 = date1 + 1.week – 3.days + 6.hours • More utility methods expected for inputting and outputting dates • Potentially will change with JSR 310 Agile 2007 - 27 Submission631©ASERT2007
  • 28. ...Dates Agile 2007 - 28 Submission631©ASERT2007 import static java.util.Calendar.getInstance as now import org.codehaus.groovy.runtime.TimeCategory import java.text.SimpleDateFormat println now().time def date = new Date() + 1 println date use(TimeCategory) { println new Date() + 1.hour + 3.weeks - 2.days } input = "1998-06-03" df1 = new SimpleDateFormat("yyyy-MM-dd") date = df1.parse(input) df2 = new SimpleDateFormat("MMM/dd/yyyy") println 'Date was ' + df2.format(date) Thu Jun 28 10:10:34 EST 2007 Fri Jun 29 10:10:35 EST 2007 Tue Jul 17 11:10:35 EST 2007 Date was Jun/03/1998
  • 29. Lists, Maps, Ranges • Lists – Special syntax for list literals – Additional common methods (operator overloading) • Maps – Special syntax for map literals – Additional common methods • Ranges – Special syntax for various kinds of ranges Agile 2007 - 29 Submission631©ASERT2007 def list = [3, new Date(), 'Jan'] assert list + list == list * 2 def map = [a: 1, b: 2] assert map['a'] == 1 && map.b == 2 def letters = 'a'..'z' def numbers = 0..<10
  • 30. Agile 2007 - 30 Submission631©ASERT2007 Lists assert [1,2,3,4] == (1..4) assert [1,2,3] + [1] == [1,2,3,1] assert [1,2,3] << 1 == [1,2,3,1] assert [1,2,3,1] - [1] == [2,3] assert [1,2,3] * 2 == [1,2,3,1,2,3] assert [1,[2,3]].flatten() == [1,2,3] assert [1,2,3].reverse() == [3,2,1] assert [1,2,3].disjoint([4,5,6]) assert [1,2,3].intersect([4,3,1]) == [3,1] assert [1,2,3].collect{ it+3 } == [4,5,6] assert [1,2,3,1].unique().size() == 3 assert [1,2,3,1].count(1) == 2 assert [1,2,3,4].min() == 1 assert [1,2,3,4].max() == 4 assert [1,2,3,4].sum() == 10 assert [4,2,1,3].sort() == [1,2,3,4] assert [4,2,1,3].findAll{ it%2 == 0 } == [4,2]
  • 31. Agile 2007 - 31 Submission631©ASERT2007 Maps… def map = [a:1, 'b':2] println map // ["a":1, "b":2] println map.a // 1 println map['a'] // 1 println map.keySet() // ["a", "b"] map = [:] // extend the map through assignment map[1] = 'a'; map[2] = 'b' map[true] = 'p'; map[false] = 'q' map[null] = 'x'; map['null'] = 'z' assert map == [ 1:'a', 2:'b', (true):'p', (false):'q', (null):'x', 'null':'z' ] def sb = new StringBuffer() [1:'a', 2:'b', 3:'c'].each{ k, v-> sb << "$k:$v, " } assert sb.toString() == '1:a, 2:b, 3:c, ' map = [1:'a', 2:'b', 3:'c'] def string = map.collect{ k, v -> "$k:$v" }.join(', ') assert string == '1:a, 2:b, 3:c'
  • 32. Agile 2007 - 32 Submission631©ASERT2007 …Maps assert [ [ name: 'Clark', city: 'London' ], [ name: 'Sharma', city: 'London' ], [ name: 'Maradona', city: 'LA' ], [ name: 'Zhang', city: 'HK' ], [ name: 'Ali', city: 'HK' ], [ name: 'Liu', city: 'HK' ] ].groupBy { it.city } == [ London: [ [ name: 'Clark', city: 'London' ], [ name: 'Sharma', city: 'London' ] ], LA: [ [ name: 'Maradona', city: 'LA' ] ], HK: [ [ name: 'Zhang', city: 'HK' ], [ name: 'Ali', city: 'HK' ], [ name: 'Liu', city: 'HK' ] ] ]
  • 33. Agile 2007 - 33 Submission631©ASERT2007 Regular Expressions… assert "Hello World!" =~ /Hello/ // Find operator assert "Hello World!" ==~ /Hellob.*/ // Match operator def p = ~/Hellob.*/ // Pattern operator assert p.class.name == 'java.util.regex.Pattern' // replace matches with calculated values assert "1.23".replaceAll(/./){ ch -> ch.next() } == '2/34' assert "1.23".replaceAll(/d/){ num -> num.toInteger() + 1 } == '2.34' assert "1.23".replaceAll(/d+/){ num -> num.toInteger() + 1 } == '2.24'
  • 34. Agile 2007 - 34 Submission631©ASERT2007 …Regular Expressions str = 'groovy.codehaus.org and www.aboutgroovy.com' re = '''(?x) # to enable whitespace and comments ( # capture the hostname in $1 (?: # these parens for grouping only (?! [-_] ) # neither underscore nor dash lookahead [w-] + # hostname component . # and the domain dot ) + # now repeat whole thing a few times [A-Za-z] # next must be a letter [w-] + # now trailing domain part ) # end of $1 capture ''' finder = str =~ re out = str (0..<finder.count).each{ adr = finder[it][0] out = out.replaceAll(adr, "$adr [${InetAddress.getByName(adr).hostAddress}]") } println out // => groovy.codehaus.org [63.246.7.187] // and www.aboutgroovy.com [63.246.7.76]
  • 35. if (1) // ... if (object) // ... if (list) // ... for (item in iterable) { } myMap.each { key, value -> println "$key : $value" } // if (condition)... // else if (condition) ... // else ... // throw, catch, finally // for, while // eachWithIndex, eachLine, ... Agile 2007 - 35 Submission631©ASERT2007 Control Structures switch (10) { case 0 : /* F */ ; break case 0..9 : // F case [8,9,11] : // F case Float : // F case {it % 3 == 0} : // F case ~/../ : // T default : // F } // implement // boolean isCase(candidate)
  • 36. Agile 2007 - 36 Submission631©ASERT2007 GroovyBeans and GPath class Dir { String name List dirs } def root = new Dir (name: '/', dirs: [ new Dir (name: 'a'), new Dir (name: 'b') ]) assert root.dirs[0].name == 'a' assert root.dirs.name == ['a', 'b'] assert root.dirs.name*.size() == [1, 1] def expected = ['getName', 'setName', 'getDirs', 'setDirs'] def accessorMethods = Dir.methods.name.grep(~/(g|s)et.*/) assert accessorMethods.intersect(expected) == expected // find, findAll, grep, every, any, …
  • 37. Static Imports Agile 2007 - 37 Submission631©ASERT2007 import static java.awt.Color.LIGHT_GRAY import static Boolean.FALSE as F import static Calendar.getInstance as now println LIGHT_GRAY // => java.awt.Color[r=192,g=192,b=192] println !F // => true println now().time // => Sun Apr 29 11:12:43 EST 2007 import static Integer.* println "Integers are between $MIN_VALUE and $MAX_VALUE" // => Integers are between -2147483648 and 2147483647 def toHexString(int val, boolean upperCase) { def hexval = upperCase ? toHexString(val).toUpperCase() : toHexString(val) return '0x' + hexval } println toHexString(15, true) // => 0xF println toHexString(15, false) // => 0xf import static Math.* assert cos(2 * PI) == 1.0 Works with Java 1.4 Slightly more powerful than Java equivalent
  • 38. Agile 2007 - 38 Submission631©ASERT2007 Topics • Introduction • Language Basics Closures • Builders • Data Access • Other Features • Testing with Groovy • Further Integration • Grails • More Information
  • 39. Using Closures... • Traditional mainstream languages – Data can be stored in variables, passed around, combined in structured ways to form more complex data; code stays put where it is defined • Languages supporting closures – Data and code can be stored in variables, passed around, combined in structured ways to form more complex algorithms and data Agile 2007 - 39 Submission631©ASERT2007 doubleNum = { num -> num * 2 } println doubleNum(3) // => 6 processThenPrint = { num, closure -> num = closure(num); println "num is $num" } processThenPrint(3, doubleNum) // => num is 6 processThenPrint(10) { it / 2 } // => num is 5
  • 40. ...Using Closures... Agile 2007 - 40 Submission631©ASERT2007 import static Math.* piA = { 22 / 7 } piB = { 333/106 } piC = { 355/113 } piD = { 0.6 * (3 + sqrt(5)) } piE = { 22/17 + 37/47 + 88/83 } piF = { sqrt(sqrt(2143/22)) } howCloseToPI = { abs(it.value() - PI) } algorithms = [piA:piA, piB:piB, piC:piC, piD:piD, piE:piE, piF:piF] findBestPI(algorithms) def findBestPI(map) { map.entrySet().sort(howCloseToPI).each { entry -> def diff = howCloseToPI(entry) println "Algorithm $entry.key differs by $diff" } } Algorithm piE differs by 1.0206946399193839E-11 Algorithm piF differs by 1.0070735356748628E-9 Algorithm piC differs by 2.668102068170697E-7 Algorithm piD differs by 4.813291008076703E-5 Algorithm piB differs by 8.321958979307098E-5 Algorithm piA differs by 0.001264489310206951
  • 41. ...Using Closures... • Used for many things in Groovy: • Iterators • Callbacks • Higher-order functions • Specialized control structures • Dynamic method definition • Resource allocation • Threads • Continuations Agile 2007 - 41 Submission631©ASERT2007 def houston(Closure doit) { (10..1).each { count -> doit(count) } } houston { println it } new File('/x.txt').eachLine { println it } 3.times { println 'Hi' } [0, 1, 2].each { number -> println number } [0, 1, 2].each { println it} def printit = { println it } [0, 1, 2].each printit
  • 42. Agile 2007 - 42 Submission631©ASERT2007 ...Using Closures... map = ['a': 1, 'b': 2] map.each {key, value -> map[key] = value * 2} assert map == ['a': 2, 'b': 4] doubler = {key, value -> map[key] = value * 2} map.each(doubler) assert map == ['a': 4, 'b': 8] def doubleMethod(entry) { map[entry.key] = entry.value * 2 } doubler = this.&doubleMethod map.each(doubler) assert map == ['a': 8, 'b': 16]
  • 43. Agile 2007 - 43 Submission631©ASERT2007 ...Using Closures... assert [1, 2, 3].grep{ it < 3 } == [1, 2] assert [1, 2, 3].any{ it % 2 == 0 } assert [1, 2, 3].every{ it < 4 } assert (1..9).collect{it}.join() == '123456789' assert (1..4).collect{it * 2}.join() == '2468' def add = { x, y -> x + y } def mult = { x, y -> x * y } assert add(1, 3) == 4 assert mult(1, 3) == 3 def min = { x, y -> [x, y].min() } def max = { x, y -> [x, y].max() } def triple = mult.curry(3); assert triple(2) == 6 def atLeastTen = max.curry(10) assert atLeastTen(5) == 10 assert atLeastTen(15) == 15
  • 44. Agile 2007 - 44 Submission631©ASERT2007 ...Using Closures def pairWise(list, Closure invoke) { if (list.size() < 2) return [] def next = invoke(list[0], list[1]) return [next] + pairWise(list[1..-1], invoke) } // using min, max, etc. From previous slide assert pairWise(1..5, add) == [3, 5, 7, 9] assert pairWise(1..5, mult) == [2, 6, 12, 20] assert pairWise(1..5, min) == [1, 2, 3, 4] assert pairWise(1..5, max) == [2, 3, 4, 5] assert 'cbaxabc' == ['a', 'b', 'c'].inject('x') { result, item -> item + result + item }
  • 45. Agile 2007 - 45 Submission631©ASERT2007 Topics • Introduction • Language Basics • Closures Builders • Data Access • Other Features • Testing with Groovy • Further Integration • Grails • More Information
  • 46. Agile 2007 - 46 Submission631©ASERT2007 Builder Pattern Inclusive import groovy.xml.* def page = new MarkupBuilder() page.html { head { title 'Hello' } body { ul { for (count in 1..10) { li "world $count" } } } } • Create new builder • Call pretended methods (html, head, ...) • Arguments are Closures • Builder code looks very declarative but is ordinary Groovy program code and can contain any kind of logic • Builder pattern from the GoF at the syntax-level • Represents easily any nested tree-structured data NodeBuilder, DomBuilder, SwingBuilder, AntBuilder, …
  • 47. Agile 2007 - 47 Submission631©ASERT2007 SwingBuilder import java.awt.FlowLayout builder = new groovy.swing.SwingBuilder() langs = ["Groovy", "Ruby", "Python", "Pnuts"] gui = builder.frame(size: [290, 100], title: 'Swinging with Groovy!’) { panel(layout: new FlowLayout()) { panel(layout: new FlowLayout()) { for (lang in langs) { checkBox(text: lang) } } button(text: 'Groovy Button', actionPerformed: { builder.optionPane(message: 'Indubitably Groovy!'). createDialog(null, 'Zen Message').show() }) button(text: 'Groovy Quit', actionPerformed: {System.exit(0)}) } } gui.show() Source: http://www.ibm.com/developerworks/java/library/j-pg04125/
  • 48. Agile 2007 - 48 Submission631©ASERT2007 AntBuilder def ant = new AntBuilder() ant.echo("hello") // lets just call one task // create a block of Ant using the builder pattern ant.sequential { myDir = "target/AntTest/" mkdir(dir: myDir) copy(todir: myDir) { fileset(dir: "src/test") { include(name: "**/*.groovy") } } echo("done") } // now lets do some normal Groovy again file = new File("target/test/AntTest.groovy") assert file.exists()
  • 49. Agile 2007 - 49 Submission631©ASERT2007 Using AntLibs: Maven Ant Tasks & AntUnit… def ant = new AntBuilder() items = [[groupId:'jfree', artifactId:'jfreechart', version:'1.0.5'], [groupId:'jfree', artifactId:'jcommon', version:'1.0.9']] def mvn = new AntLibHelper(ant:ant, namespace:'org.apache.maven.artifact.ant') def antunit = new AntLibHelper(ant:ant, namespace:'org.apache.ant.antunit') // download artifacts with Maven Ant tasks mvn.dependencies(filesetId:'artifacts') { items.each{ dependency(it) } } // print out what we downloaded ant.fileScanner { fileset(refid:'artifacts') }.each { println it } // use AntUnit to confirm expected files were downloaded def prefix = System.properties.'user.home' + '/.m2/repository' items.each { item -> def g = item.groupId def a = item.artifactId def v = item.version antunit.assertFileExists(file:"$prefix/$g/$a/$v/$a-${v}.jar") }
  • 50. Agile 2007 - 50 Submission631©ASERT2007 …Using AntLibs: Maven Ant Tasks & AntUnit ... class AntLibHelper { def namespace, ant Object invokeMethod(String name, Object params) { ant."antlib:$namespace:$name"(*params) } } /* => Downloading: jfree/jfreechart/1.0.5/jfreechart-1.0.5.pom Transferring 1K Downloading: jfree/jcommon/1.0.9/jcommon-1.0.9.pom Transferring 1K Downloading: jfree/jfreechart/1.0.5/jfreechart-1.0.5.jar Transferring 1157K Downloading: jfree/jcommon/1.0.9/jcommon-1.0.9.jar Transferring 298K C:UsersPaul.m2repositoryjfreejcommon1.0.9jcommon-1.0.9.jar C:UsersPaul.m2repositoryjfreejfreechart1.0.5jfreechart-1.0.5.jar */
  • 51. Agile 2007 - 51 Submission631©ASERT2007 Topics • Introduction • Language Basics • Closures • Builders Data Access Objects XML Databases • Other Features • Testing with Groovy • Further Integration • Grails • More Information
  • 52. Uniform Data Access • Data Access to Objects, XML, SQL Databases has some differences and some similarities – Differences in detail about how to set up structures – Similarities in how to process data within resulting structures Agile 2007 - 52 Submission631©ASERT2007
  • 53. Agile 2007 - 53 Submission631©ASERT2007 Object Data Access… import java.text.SimpleDateFormat class Athlete { def firstname, lastname, gender, country, dateOfBirth } def asDate(dateStr) { new SimpleDateFormat("yyyy-MM-dd").parse(dateStr) } def athletes = [ new Athlete(firstname: 'Paul', lastname: 'Tergat', dateOfBirth: '1969-06-17', gender: 'M', country: 'KEN'), new Athlete(firstname: 'Khalid', lastname: 'Khannouchi', dateOfBirth: '1971-12-22', gender: 'M', country: 'USA'), new Athlete(firstname: 'Sammy', lastname: 'Korir', dateOfBirth: '1971-12-12', gender: 'M', country: 'KEN'), new Athlete(firstname: 'Ronaldo', lastname: 'da Costa', dateOfBirth: '1970-06-07', gender: 'M', country: 'BRA'), new Athlete(firstname: 'Paula', lastname: 'Radcliffe', dateOfBirth: '1973-12-17', gender: 'F', country: 'GBR') ]
  • 54. Agile 2007 - 54 Submission631©ASERT2007 …Object Data Access def bornSince70 = { asDate(it.dateOfBirth) > asDate('1970-1-1') } def excludingKh = { it.lastname <= 'Kg' || it.lastname >= 'Ki' } def byGenderDescThenByCountry = { a, b -> a.gender == b.gender ? a.country <=> b.country : b.gender <=> a.gender } def someYoungsters = athletes. findAll(bornSince70). findAll(excludingKh). sort(byGenderDescThenByCountry) someYoungsters.each { def name = "$it.firstname $it.lastname".padRight(25) println "$name ($it.gender) $it.country $it.dateOfBirth" }
  • 55. Agile 2007 - 55 Submission631©ASERT2007 Xml Data Access… import java.text.SimpleDateFormat import com.thoughtworks.xstream.XStream class Athlete { String firstname, lastname, gender, country, dateOfBirth } def asDate(dateStr) { new SimpleDateFormat("yyyy-MM-dd").parse(dateStr) } def athleteList = [ new Athlete(firstname: 'Paul', lastname: 'Tergat‘, dateOfBirth: '1969-06-17', gender: 'M', country: 'KEN'), new Athlete(firstname: 'Khalid', lastname: 'Khannouchi', dateOfBirth: '1971-12-22', gender: 'M', country: 'USA'), new Athlete(firstname: 'Sammy', lastname: 'Korir', dateOfBirth: '1971-12-12', gender: 'M', country: 'KEN'), new Athlete(firstname: 'Ronaldo', lastname: 'da Costa', dateOfBirth: '1970-06-07', gender: 'M', country: 'BRA'), new Athlete(firstname: 'Paula', lastname: 'Radcliffe', dateOfBirth: '1973-12-17', gender: 'F', country: 'GBR') ] // create XML as input def input = new XStream().toXML(athleteList)
  • 56. Agile 2007 - 56 Submission631©ASERT2007 …Xml Data Access def athletes = new XmlSlurper().parseText(input).Athlete def bornSince70 = { asDate(it.dateOfBirth.text()) > asDate('1970-1-1') } def excludingKh = { it.lastname.text() <= 'Kg' || it.lastname.text() >= 'Ki' } def byGenderDescThenByCountry = { a, b -> a.gender.text() == b.gender.text() ? a.country.text() <=> b.country.text() : b.gender.text() <=> a.gender.text() } def someYoungsters = athletes. findAll(bornSince70).findAll(excludingKh).list(). sort(byGenderDescThenByCountry) someYoungsters.each { def name = "$it.firstname $it.lastname".padRight(25) println "$name ($it.gender) $it.country $it.dateOfBirth" }
  • 57. Agile 2007 - 57 Submission631©ASERT2007 Sql Data Access… import groovy.sql.Sql dbHandle = null def getDb() { if (dbHandle) return dbHandle def source = new org.hsqldb.jdbc.jdbcDataSource() source.database = 'jdbc:hsqldb:mem:GIA' source.user = 'sa' source.password = '' dbHandle = new Sql(source) return dbHandle } db.execute ''' DROP INDEX athleteIdx IF EXISTS; DROP TABLE Athlete IF EXISTS; CREATE TABLE Athlete ( athleteId INTEGER GENERATED BY DEFAULT AS IDENTITY, firstname VARCHAR(64), lastname VARCHAR(64), country VARCHAR(3), gender CHAR(1), dateOfBirth DATE ); CREATE INDEX athleteIdx ON Athlete (athleteId); '''
  • 58. Agile 2007 - 58 Submission631©ASERT2007 …Sql Data Access… def athleteList = [ [firstname: 'Paul', lastname: 'Tergat', dateOfBirth: '1969-06-17', gender: 'M', country: 'KEN'], [firstname: 'Khalid', lastname: 'Khannouchi', dateOfBirth: '1971-12-22', gender: 'M', country: 'USA'], [firstname: 'Sammy', lastname: 'Korir', dateOfBirth: '1971-12-12', gender: 'M', country: 'KEN'], [firstname: 'Ronaldo', lastname: 'da Costa', dateOfBirth: '1970-06-07', gender: 'M', country: 'BRA'], [firstname: 'Paula', lastname: 'Radcliffe', dateOfBirth: '1973-12-17', gender: 'F', country: 'GBR'] ] def athletes = db.dataSet('Athlete') athleteList.each {a -> athletes.add(a)}
  • 59. def bornSince70 = { it.dateOfBirth > '1970-1-1' } def excludingKh = { it.lastname <= 'Kg' || it.lastname >= 'Ki' } def someYoungsters = athletes. findAll(bornSince70). findAll(excludingKh). sort { it.gender }.reverse(). // * experimental sort { it.country } // * experimental println someYoungsters.sql + 'n' + someYoungsters.parameters someYoungsters.each { def name = "$it.firstname $it.lastname".padRight(25) println "$name ($it.gender) $it.country $it.dateOfBirth" } /* => select * from Athlete where dateOfBirth > ? and (lastname <= ? or lastname >= ?) order by gender DESC, country ["1970-1-1", "Kg", "Ki"] Ronaldo da Costa (M) BRA 1970-06-07 Sammy Korir (M) KEN 1971-12-12 Paula Radcliffe (F) GBR 1973-12-17 */ Agile 2007 - 59 Submission631©ASERT2007 …Sql Data Access
  • 60. Agile 2007 - 60 Submission631©ASERT2007 More Details: XmlSlurper… • Features – One-line parsing – GPath Syntax – Efficient lazy evaluation static def CAR_RECORDS = ''' <records> <car name='HSV Maloo' make='Holden' year='2006'> <country>Australia</country> <record type='speed'>Production Pickup Truck with speed of 271kph</record> </car> <car name='P50' make='Peel' year='1962'> <country>Isle of Man</country> <record type='size'>Smallest Street-Legal Car at 99cm wide and 59 kg in weight</record> </car> <car name='Royale' make='Bugatti' year='1931'> <country>France</country> <record type='price'>Most Valuable Car at $15 million</record> </car> </records> '''
  • 61. Agile 2007 - 61 Submission631©ASERT2007 …More Details: XmlSlurper def records = new XmlSlurper().parseText(XmlExamples.CAR_RECORDS) // 3 records in total assert 3 == records.car.size() // 10 nested nodes assert 10 == records.depthFirst().collect{ it }.size() // test properties of the first record def firstRecord = records.car[0] assert 'car' == firstRecord.name() assert 'Holden' == firstRecord.@make.toString() assert 'Australia' == firstRecord.country.text() // 2 cars have an 'e' in the make assert 2 == records.car.findAll{ it.@make.toString().contains('e') }.size() // 2 cars have an 'e' in the make assert 2 == records.car.findAll{ it.@make =~ '.*e.*' }.size() // makes of cars that have an 's' followed by an 'a' in the country assert ['Holden', 'Peel'] == records.car.findAll{ it.country =~ '.*s.*a.*' }.@make.collect{ it.toString // types of records assert ['speed', 'size', 'price'] == records.depthFirst().grep{ it.@type != '' }.'@type'*.toString() assert ['speed', 'size', 'price'] == records.'**'.grep{ it.@type != '' }.'@type'*.toString() // check parent() operator def countryOne = records.car[1].country assert 'Peel' == countryOne.parent().@make.toString() assert 'Peel' == countryOne.'..'.@make.toString() // names of cars with records sorted by year def names = records.car.list().sort{ it.@year.toInteger() }.'@name'*.toString() assert ['Royale', 'P50', 'HSV Maloo'] == names
  • 62. Agile 2007 - 62 Submission631©ASERT2007 More Details: Working with Databases • Using standard SQL statements • Using DataSets import groovy.sql.Sql def foo = 'cheese' def sql = Sql.newInstance("jdbc:mysql://localhost:3306/mydb", "user", "pswd", "com.mysql.jdbc.Driver") sql.eachRow("select * from FOOD where type=${foo}") { println "Gromit likes ${it.name}" } import groovy.sql.Sql def sql = Sql.newInstance("jdbc:mysql://localhost:3306/mydb", "user", "pswd", "com.mysql.jdbc.Driver") def food = sql.dataSet('FOOD') def cheese = food.findAll { it.type == 'cheese' } cheese.each { println "Eat ${it.name}" }
  • 63. DataSets and Lazy Evaluation Agile 2007 - 63 Submission631©ASERT2007 athleteSet = db.dataSet('Athlete') youngsters = athleteSet.findAll{ it.dateOfBirth > '1970-1-1'} paula = youngsters.findAll{ it.firstname == 'Paula'} println paula.sql // => // select * from Athlete where dateOfBirth > ? and firstname = ? println paula.parameters // => // [1970-1-1, Paula] paula.each { println it.lastname } // database called here // => // Radcliffe
  • 64. Agile 2007 - 64 Submission631©ASERT2007 Topics • Introduction • Language Basics • Closures • Builders • Data Access Other Features • Testing with Groovy • Further Integration • Grails • More Information
  • 65. Agile 2007 - 65 Submission631©ASERT2007 JMX import javax.management.remote.* import javax.management.* import javax.naming.Context def urlRuntime = '/jndi/weblogic.management.mbeanservers.runtime' def urlBase = 'service:jmx:t3://localhost:7001' def serviceURL = new JMXServiceURL(urlBase + urlRuntime) def h = new Hashtable() h.put(Context.SECURITY_PRINCIPAL, 'weblogic') h.put(Context.SECURITY_CREDENTIALS, 'weblogic') h.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, 'weblogic.management.remote') def server = JMXConnectorFactory.connect(serviceURL, h).mBeanServerConnection def domainName = new ObjectName('com.bea:Name=RuntimeService,' + 'Type=weblogic.management.mbeanservers.runtime.RuntimeServiceMBean') def rtName = server.getAttribute(domainName, 'ServerRuntime') def rt = new GroovyMBean(server, rtName) println "Server: name=$rt.Name, state=$rt.State, version=$rt.WeblogicVersion" def destFilter = Query.match(Query.attr('Type'), Query.value( 'JMSDestinationRuntime')) server.queryNames(new ObjectName('com.bea:*'), destFilter).each {name -> def jms = new GroovyMBean(server, name) println "JMS Destination: name=$jms.Name, type=$jms.DestinationType" + ", messages=$jms.MessagesReceivedCount" }
  • 66. Agile 2007 - 66 Submission631©ASERT2007 ExpandoMetaClass… String.metaClass.swapCase = {-> def sb = new StringBuffer() delegate.each { sb << (Character.isUpperCase(it as char) ? Character.toLowerCase(it as char) : Character.toUpperCase(it as char)) } sb.toString() } List.metaClass.sizeDoubled = {-> delegate.size() * 2 } LinkedList list = [] list << 1 list << 2 assert 4 == list.sizeDoubled()
  • 67. Agile 2007 - 67 Submission631©ASERT2007 …ExpandoMetaClass class Person { String name } class MortgageLender { def borrowMoney() { "buy house" } } def lender = new MortgageLender() Person.metaClass.buyHouse = lender.&borrowMoney def p = new Person() assert "buy house" == p.buyHouse()
  • 68. Agile 2007 - 68 Submission631©ASERT2007 Constraint Programming // require(url:'http://www.alice.unibo.it/tuProlog/', jar:'tuprolog.jar‘, version:'2.1') import alice.tuprolog.* /** Pretty Printing */ def pprint(soln) { println soln.isSuccess() ? "$soln.query = $soln.solution" : 'no solution found' } /** Prolog clauses */ def getTheory() { new Theory(""“ parent(pam, bob). parent(tom, bob). parent(tom, liz). parent(bob, ann). parent(bob, pat). parent(pat, jim). female(pam). male(tom). male(bob). female(liz). female(pat). female(ann). male(jim). offspring(X,Y) :- parent(Y,X). ... mother(X,Y) :- parent(X,Y), female(X). father(X,Y) :- parent(X,Y), male(X). grandparent(X,Z) :- parent(X,Y), parent(Y,Z). grandmother(X,Y) :- grandparent(X,Y), female(X). grandfather(X,Y) :- grandparent(X,Y), male(X). sister(X,Y) :- parent(Z,X), parent(Z,Y), female(X). brother(X,Y) :- parent(Z,X), parent(Z,Y), male(X). ancestor(X,Y) :- parent(X,Y). ancestor(X,Z) :- parent(X,Y), ancestor(Y,Z). """) } … def engine = new Prolog() engine.theory = theory pprint engine.solve('ancestor(tom,X).') while(engine.hasOpenAlternatives()) { pprint engine.solveNext() } ancestor(tom,X) = ancestor(tom,bob) ancestor(tom,X) = ancestor(tom,liz) ancestor(tom,X) = ancestor(tom,ann) ancestor(tom,X) = ancestor(tom,pat) ancestor(tom,X) = ancestor(tom,jim) no solution found
  • 69. Agile 2007 - 69 Submission631©ASERT2007 Functional Programming def fac(n) { n == 0 ? 1 : n * fac(n - 1) } assert 24 == fac(4) // now define and use infinite streams def integers(n) { cons(n, { integers(n+1) }) } def naturalnumbers = integers(1) assert '1 2 3 4 5 6 7 8 9 10' == naturalnumbers.take(10).join(' ') def evennumbers = naturalnumbers.filter{ it % 2 == 0 } assert '2 4 6 8 10 12 14 16 18 20' == evennumbers.take(10).join(' ')
  • 70. Agile 2007 - 70 Submission631©ASERT2007 Using XStream - Vanilla // require(groupId:'com.thoughtworks.xstream', artifactId:'xstream', version:'1.2.2') // require(groupId:'xpp3', artifactId:'xpp3_min', version:'1.1.3.4.O') import com.thoughtworks.xstream.* class Staff { String firstname, lastname, position } def stream = new XStream() def msg = new Staff(firstname:'John', lastname:'Connor', position:'Resistance Leader') println stream.toXML(msg) <Staff> <firstname class="string">John</firstname> <lastname class="string">Connor</lastname> <position class="string">Resistance Leader</position> </Staff>
  • 71. Agile 2007 - 71 Submission631©ASERT2007 Using XStream - Annotations import com.thoughtworks.xstream.* import com.thoughtworks.xstream.annotations.* @XStreamAlias("person") class Associate { @XStreamAsAttribute @XStreamAlias('first-name') private String firstname @XStreamAlias('surname') private String lastname @XStreamOmitField private String position } msg = new Associate(firstname:'Sarah', lastname:'Connor', position:'Protector') Annotations.configureAliases(stream, Associate) println stream.toXML(msg) <person first-name="Sarah"> <surname>Connor</surname> </person>
  • 72. Agile 2007 - 72 Submission631©ASERT2007 Topics • Introduction • Language Basics • Closures • Builders • Data Access • Other Features Testing with Groovy Unit Testing Mocks Acceptance Testing • Further Integration • Grails • More Information
  • 73. Agile 2007 - 73 Submission631©ASERT2007 Built-in JUnit • Groovy distribution includes junit (3.8.2) • Automatically invokes text runner • Has some useful extensions class GroovyMultiplierJUnit3Test extends GroovyTestCase { void testPositives() { def testee = new GroovyMultiplier() assertEquals "+ve multiplier error", 9, testee.triple(3) assertEquals "+ve multiplier error", 12, testee.triple(4) } void testNegatives() { def testee = new GroovyMultiplier() assertEquals "-ve multiplier error", -12, testee.triple(-4) } } .. Time: 0.092 OK (2 tests)
  • 74. Agile 2007 - 74 Submission631©ASERT2007 JUnit 4.4 import org.junit.Test import org.junit.runner.JUnitCore import static org.junit.Assert.assertEquals class ArithmeticTest { @Test void additionIsWorking() { assertEquals 4, 2+2 } @Test(expected=ArithmeticException) void divideByZero() { println 1/0 } } JUnitCore.main(ArithmeticTest.name)
  • 75. Agile 2007 - 75 Submission631©ASERT2007 JUnit 4.4 Parameterized Tests import org.junit.Test import org.junit.Before import org.junit.runner.RunWith import org.junit.runner.JUnitCore import org.junit.runners.Parameterized import org.junit.runners.Parameterized.* @RunWith(Parameterized) class GroovyMultiplierJUnit4Test { def testee def param def expected @Parameters static data() { return (2..4).collect{ [it, it * 3] as Integer[] } } GroovyMultiplierJUnit4Test(a, b) { param = a expected = b } @Before void setUp() { testee = new GroovyMultiplier() } @Test void positivesFixed() { assert testee.triple(1) ==3 } @Test void positivesParameterized() { assert testee.triple(param) == expected } @Test void negativesParameterized() { assert testee.triple(-param) == -expected } } JUnitCore.main('GroovyMultiplierJUnit4Test')
  • 76. Agile 2007 - 76 Submission631©ASERT2007 JUnit 4.4 Theories … import org.junit.runner.* import org.junit.experimental.theories.* import static org.junit.Assume.assumeTrue @RunWith(Theories) class LanguageTheoryTest { @DataPoint public static String java = 'Java' @DataPoint public static String ruby = 'JRuby' @DataPoint public static String python = 'Jython' @DataPoint public static String javascript = 'Rhino' @DataPoint public static String groovy = 'Groovy' @DataPoint public static String scala = 'Scala' @DataPoint public static String csharp = 'C#' def jvmLanguages = [java, ruby, python, groovy, scala, javascript] def teamSkills = [ tom: [java, groovy, ruby], dick: [csharp, scala, java, python], harry: [javascript, groovy, java] ] ...
  • 77. Agile 2007 - 77 Submission631©ASERT2007 … JUnit 4.4 Theories ... @Theory void everyoneKnowsJava() { teamSkills.each { developer, skills -> assert java in skills } } @Theory void teamKnowsEachJvmLanguage(String language) { assumeTrue language in jvmLanguages assert teamSkills.any { mapEntry -> language in mapEntry.value } } } JUnitCore.main(LanguageTheoryTest.name)
  • 78. Agile 2007 - 78 Submission631©ASERT2007 Popper @RunWith(Theories) class PopperBetweenTest extends GroovyTheoryContainer { private int test, total // for explanatory purposes only @Theory void multiplyIsInverseOfDivide( @Between(first = -4, last = 2) int amount, @Between(first = -2, last = 5) int m ) { total++ assume m != 0 assert new Dollar(amount).times(m).divideBy(m).amount == amount test++ } @After void dumpLog() { println "$test tests performed out of $total combinations" } } JUnitCore.main('PopperBetweenTest') JUnit version 4.3.1 .49 tests performed out of 56 combinations Time: 0.234 OK (1 test)
  • 79. Agile 2007 - 79 Submission631©ASERT2007 Instinct import com.googlecode.instinct.marker.annotate.BeforeSpecification as initially import com.googlecode.instinct.marker.annotate.Specification as spec import static com.googlecode.instinct.runner.TextContextRunner.runContexts as check_specs_for class a_default_storer { def storer @initially void create_new_storer() { storer = new Storer() } private check_persist_and_reverse(value, reverseValue) { storer.put(value) assert value == storer.get() assert reverseValue == storer.reverse } @spec def should_reverse_numbers() { check_persist_and_reverse 123.456, -123.456 } @spec def should_reverse_strings() { check_persist_and_reverse 'hello', 'olleh' } @spec def should_reverse_lists() { check_persist_and_reverse([1, 3, 5], [5, 3, 1]) } } check_specs_for a_default_storer a_default_storer - should_reverse_lists - should_reverse_strings - should_reverse_numbers
  • 80. Agile 2007 - 80 Submission631©ASERT2007 Built-in Mocks for Groovy… • Handle statics, explicit constructors, etc. import groovy.mock.interceptor.MockFor def mocker = new MockFor(Collaborator.class) // create the Mock support mocker.demand.one(1..2) { 1 } // demand the 'one' method one or two times, returning 1 mocker.demand.two() { 2 } // demand the 'two' method exactly once, returning 2 mocker.use { // start using the Mock def caller = new Caller() // caller will call Collaborator assertEquals 1, caller.collaborateOne() // will call Collaborator.one assertEquals 1, caller.collaborateOne() // will call Collaborator.one assertEquals 2, caller.collaborateTwo() // will call Collaborator.two } // implicit verify for strict expectation here
  • 81. …Built-in Mocks for Groovy Agile 2007 - 81 Submission631©ASERT2007 import groovy.mock.interceptor.MockFor class MockingTest extends GroovyTestCase { def mock1 = new MockFor(Collaborator1) def mock2 = new MockFor(Collaborator2) def mock3 = new MockFor(Collaborator3) private static final Closure DONTCARE = null private static final Closure PASS = {} private static final Closure FAIL = { throw new RuntimeException() } void testSuccess() { check(PASS, PASS, DONTCARE) } void testCollaborator1Fails() { check(FAIL, DONTCARE, PASS) } void testCollaborator2Fails() { check(PASS, FAIL, PASS) } private check(expected1, expected2, expected3){ if (expected1) mock1.demand.method1(expected1) if (expected2) mock2.demand.method2(expected2) if (expected3) mock3.demand.method3(expected3) mock1.use { mock2.use { mock3.use { new Mocking().method() }}} } } class Mocking { def method() { try { new Collaborator1().method1() new Collaborator2().method2() } catch (Exception e) { new Collaborator3().method3() } } } class Collaborator1 {} class Collaborator2 {} class Collaborator3 {}
  • 82. Agile 2007 - 82 Submission631©ASERT2007 JMock 2 import org.jmock.integration.junit4.JMock import org.jmock.Mockery import org.junit.Test import org.junit.Before import org.junit.runner.RunWith import org.junit.runner.JUnitCore @RunWith(JMock) class JMock2Test { Mockery context = new JUnit4GroovyMockery() def mockReverser, storer @Before void setUp() throws Exception { mockReverser = context.mock(Reverser.class) storer = new JavaStorer(mockReverser) } @Test void testStorage() { expectReverse(123.456, -123.456) expectReverse('hello', 'olleh') checkReverse(123.456, -123.456) checkReverse('hello', 'olleh') } def expectReverse(input, output) { context.checking{ one(mockReverser).reverse(input); will(returnValue(output)) } } … … def checkReverse(value, reverseValue) { storer.put(value) assert value == storer.get() assert reverseValue == storer.getReverse() } } JUnitCore.main('JMock2Test') class Storer { ... def getReverse() { return reverser.reverse(stored) } }
  • 83. Agile 2007 - 83 Submission631©ASERT2007 WebTest testing Web Sites def ant = new AntBuilder() def webtest_home = System.properties.'webtest.home' ant.taskdef(resource:'webtest.taskdef'){ classpath(){ pathelement(location:"$webtest_home/lib") fileset(dir:"$webtest_home/lib", includes:"**/*.jar") } } def config_map = [:] ['protocol','host','port','basepath','resultfile', 'resultpath', 'summary', 'saveresponse','defaultpropertytype'].each{ config_map[it] = System.properties['webtest.'+it] } ant.testSpec(name:'groovy: Test Groovy Scripting at creation time'){ config(config_map) steps(){ invoke(url:'linkpage.html') for (i in 1..10){ verifyText(description:"verify number ${i} is on pages", text:"${i}") } } }
  • 84. Agile 2007 - 84 Submission631©ASERT2007 WebTest testing Emails def ant = new AntBuilder() def webtest_home = System.properties.'webtest.home' ant.taskdef(resource:'webtest.taskdef'){ classpath(){ pathelement(location:"$webtest_home/lib") fileset(dir:"$webtest_home/lib", includes:"**/*.jar") } } ant.testSpec(name:'Email Test'){ steps { emailSetConfig(server:'localhost', password:'password', username:'devteam@mycompany.org', type:'pop3') emailStoreMessageId(subject:'/Build notification/', property:'msg') emailStoreHeader(property:'subject', messageId:'#{msg}', headerName:'Subject') groovy('''def subject = step.webtestProperties.subject assert subject.startsWith('Build notification')''') emailMessageContentFilter(messageId:'#{msg}') verifyText(text:'Failed build') } }
  • 85. SoapUI • Tool for testing Web Services has a built- in Groovy editor for custom steps Agile 2007 - 85 Submission631©ASERT2007
  • 86. Agile 2007 - 86 Submission631©ASERT2007 Topics • Introduction • Language Basics • Closures • Builders • Data Access • Other Features • Testing with Groovy Further Integration • Grails • More Information
  • 87. Agile 2007 - 87 Submission631©ASERT2007 Integration With Existing Native Apps • Scriptom allows you to script any ActiveX or COM Windows component from within your Groovy scripts import org.codehaus.groovy.scriptom.ActiveXProxy def outlook = new ActiveXProxy("Outlook.Application") def message = outlook.CreateItem(0) def emails = "galleon@codehaus.org;glaforge@codehaus.org" def rec = message.Recipients.add(emails) rec.Type = 1 message.Display(true)
  • 88. Agile 2007 - 88 Submission631©ASERT2007 Integration With Existing Services • WS or XML/RPC allow seamless connection to existing services … – No need to generate stubs, – Complex types are supported import groovy.net.soap.SoapClient proxy = new SoapClient( "http://www.webservicex.net/CurrencyConvertor.asmx?WSDL") rate = proxy.ConversionRate("USD", "EUR") println rate
  • 89. SOAP Client and Server Agile 2007 - 89 Submission631©ASERT2007 class MathService { double add(double a, double b) { a + b } double square(double c) { c * c } } import groovy.net.soap.SoapServer def server = new SoapServer('localhost', 6789) server.setNode('MathService') server.start() import groovy.net.soap.SoapClient def math = new SoapClient('http://localhost:6789/MathServiceInterface?wsdl') assert math.add(1.0, 2.0) == 3.0 assert math.square(3.0) == 9.0
  • 90. Integration with Spring... Agile 2007 - 90 Submission631©ASERT2007 // traditional approach using a beans xml file import org.springframework.context.support.ClassPathXmlApplicationContext def ctx = new ClassPathXmlApplicationContext('calcbeans.xml') def calc = ctx.getBean('calcBean') println calc.doAdd(3, 4) // => 7 // using BeanBuilder def bb = new grails.spring.BeanBuilder() bb.beans { adder(AdderImpl) calcBean(CalcImpl2) { adder = adder } } def ctx = bb.createApplicationContext() def calc = ctx.getBean('calcBean') println calc.doAdd(3, 4) // => 7
  • 91. ...Integration with Spring Agile 2007 - 91 Submission631©ASERT2007 // using annotations import org.springframework.stereotype.Component @Component class AdderImpl { def add(x, y) { x + y } } import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @Component class CalcImpl3 { @Autowired private AdderImpl adder def doAdd(x, y) { adder.add(x, y) } } import org.springframework.context.support.GenericApplicationContext import org.springframework.context.annotation.ClassPathBeanDefinitionScanner def ctx = new GenericApplicationContext() new ClassPathBeanDefinitionScanner(ctx).scan('') // scan root package for components ctx.refresh() def calc = ctx.getBean('calcImpl3') println calc.doAdd(3, 4) // => 7
  • 92. Groovy.Net... Agile 2007 - 92 Submission631©ASERT2007 // To create dlls: // ikvmc -target:library groovy-all-1.1-SNAPSHOT.jar // Invoking groovy shell via GroovyDotNet.exe using System; using System.Collections.Generic; namespace GroovyDotNet { class MainClass { public static void Main(string[] args) { groovy.ui.InteractiveShell.main(args); } } } // you can invoke normal groovy [1, 2, 3].each { println it } // you can also invoke a .NET method cli.System.Console.WriteLine('hello world {0}', 'from Groovy')
  • 93. ...Groovy.Net Agile 2007 - 93 Submission631©ASERT2007
  • 94. Agile 2007 - 94 Submission631©ASERT2007 JFreeChart import org.jfree.chart.ChartFactory import org.jfree.data.category.DefaultCategoryDataset import org.jfree.chart.plot.PlotOrientation as Orientation import groovy.swing.SwingBuilder import javax.swing.WindowConstants as WC def dataset = new DefaultCategoryDataset() dataset.addValue 150, "no.1", "Jan" dataset.addValue 210, "no.1", "Feb" dataset.addValue 390, "no.1", "Mar" dataset.addValue 300, "no.2", "Jan" dataset.addValue 400, "no.2", "Feb" dataset.addValue 200, "no.2", "Mar" def labels = ["Bugs", "Month", "Count"] def options = [true, true, true] def chart = ChartFactory.createLineChart(*labels, dataset, Orientation.VERTICAL, *options) def swing = new SwingBuilder() def frame = swing.frame(title:'Groovy LineChart', defaultCloseOperation:WC.EXIT_ON_CLOSE) { panel(id:'canvas') { rigidArea(width:400, height:400) } } frame.pack() frame.show() chart.draw(swing.canvas.graphics, swing.canvas.bounds)
  • 95. Mashups Agile 2007 - 95 Submission631©ASERT2007
  • 96. Agile 2007 - 96 Submission631©ASERT2007 Topics • Introduction • Language Basics • Closures • Builders • Data Access • Other Features • Testing with Groovy • Further Integration Grails • More Information
  • 97. Wikipedia says: • Grails has been developed with a number of goals in mind: – Provide a high-productivity web framework for the Java platform – Offer a consistent framework that takes away confusion and is easy to learn – Offer documentation for those parts of the framework that matter for its users – Provide what users expect in areas that are often complex and inconsistent: • Powerful and consistent persistence framework • Powerful and easy to use view templates using GSP (Groovy Server Pages) • Dynamic tag libraries to easily create web page components • Good Ajax support that is easy to extend and customize – Provide sample applications that demonstrate the power of the framework – Provide a complete development mode, including web server and automatic reload of resources Agile 2007 - 97 Submission631©ASERT2007
  • 98. Goals • Grails has three properties that increase developer productivity significantly when compared to traditional Java web frameworks: – No XML configuration – Ready-to-use development environment – Functionality available through mixins Agile 2007 - 98 Submission631©ASERT2007
  • 99. Web framework • Controllers • Views Agile 2007 - 99 Submission631©ASERT2007 class BookController { def list = { [ books: Book.findAll() ] } } <html> <head> <title>Our books</title> </head> <body> <ul> <g:each it="books"> <li>${it.title} (${it.author.name})</li> </g:each> </ul> </body> </html> grails create-controller
  • 100. Persistence • Model • Methods Agile 2007 - 100 Submission631©ASERT2007 class Book { Long id Long version String title Person author } def book = new Book(title:"The Da Vinci Code", author:Author.findByName("Dan Brown")) book.save() grails create-domain-class def book = Book.find("from Book b where b.title = ?", [ 'The Da Vinci Code' ]) def books = Book.findAll() def book = Book.findByTitleLike("%Da Vinci%") def book = Book.findWhere(title:"The Da Vinci Code")
  • 101. Agile 2007 - 101 Submission631©ASERT2007 Project Structure %PROJECT_HOME% + grails-app + conf ---> location of configuration artifacts like data sources + controllers ---> location of controller artifacts + domain ---> location of domain classes + i18n ---> location of message bundles for i18n + services ---> location of services + taglib ---> location of tag libraries + util ---> location of special utility classes (e.g., codecs, etc.) + views ---> location of views + layouts ---> location of layouts + hibernate ---> optional hibernate config + lib + spring ---> optional spring config + src + groovy ---> optional; location for Groovy source files (of types other than those in grails-app/*) + java ---> optional; location for Java source files + war + WEB-INF
  • 102. Agile 2007 - 102 Submission631©ASERT2007 Topics • Introduction • Language Basics • Closures • Builders • Data Access • Other Features • Testing with Groovy • Further Integration • Grails More Information
  • 103. More Information: on the web • Web sites – http://groovy.codehaus.org – http://grails.codehaus.org – http://pleac.sourceforge.net/pleac_groovy (many examples) – http://www.asert.com.au/training/java/GV110.htm (workshop) • Mailing list for users – user@groovy.codehaus.org • Information portals – http://www.aboutgroovy.org – http://www.groovyblogs.org • Documentation (600+ pages) – Getting Started Guide, User Guide, Developer Guide, Testing Guide, Cookbook Examples, Advanced Usage Guide • Books – Several to choose from ... Agile 2007 - 103 Submission631©ASERT2007
  • 104. More Information: Groovy in Action Agile 2007 - 104 Submission631©ASERT2007