Groovy
The Dynamic Language for the JVM

Groovy
- Aug 2003: Created by James Strachan and Bob McWhirter
- Jan 2007: Groovy 1.0 released
- Nov 2011: Groovy 1.8.3 Current version, 1.9 and 2.0 in works
- Brings many dynamic/scripting features to JVM
- Influenced by SmallTalk, Python, Ruby
- Familiar syntax to Java
- Harmonious with Java Platform / Ecosystem
Groovy Uses
- Scripting on the JVM
- More productive Tests
- More productive Application Code
- Flexible Code and Libraries
- Embedded Scripting in Applications
- Groovy-based JVM Tools
Hello Groovy!
Most Java code is valid Groovy...
public class Hello { public static void main(String[] args) { System.out.println("Hello World"); } }
Hello Groovy!
Semicolons are optional...
public class Hello { public static void main(String[] args) { System.out.println("Hello World") } }
- Most Java code is valid Groovy
Hello Groovy!
Most modifiers are optional too...
class Hello { static void main(String[] args) { System.out.println("Hello World") } }
- Most Java code is valid Groovy
- Semicolons are optional
Hello Groovy!
System.out is optional too...
class Hello { static void main(String[] args) { println("Hello World") } }
- Most Java code is valid Groovy
- Semicolons are optional
- Most modifiers are optional too
Hello Groovy!
Actually, a lot of stuff is optional...
println("Hello World")
- Most Java code is valid Groovy
- Semicolons are optional
- Most modifiers are optional too
- System.out is optional too
Groovy Language Quick Tour
- Equality: equals(), == and is()
- GStrings: Strings on steroids
- Collection literals: first class Lists and Maps
- Map/Bean shortcut syntax
- Closures: Java 8? Java 9?
- Regular expressions: easy matching
- Dynamic features
Equality
Value equality with ==, reference equality with is()
def a = "Hello Groovy" def b = a.substring(6) + " " + a.substring(0, 5) assert b == "Groovy Hello" assert !b.is("Groovy Hello") def c = a assert c == a assert c.is(a)
Equality
Numbers are a little special...
def l = 123 assert l.class == Integer def d = 123.0 assert d.class == BigDecimal assert l == d assert !l.is(d) assert !l.equals(d) assert l.compareTo(d) == 0
GStrings
Strings with interpolation features...
def s = "Groovy" assert "Hello $s" == "Hello Groovy" assert !"Hello $s".is("Hello Groovy") def g = "Hello ${s}" assert "g's class is ${g.class.simpleName}" == "g's class is GStringImpl"
GStrings
Strings with multiline capability...
def m = """ SELECT * FROM Customers WHERE UPPER(CustomerName) LIKE '%' + $s + '%' """
GStrings
Careful, they are mutable too!
def s = "Groovy" def h = "${s}" def k = "${->s}" assert h == "Groovy" assert k == "Groovy" s = "JRuby" assert h == "Groovy" assert k != "Groovy"
Collection Literals
Lists are first class...
def a = ["one", "two", "three", "four", "five", "six"] assert a[0] == "one" assert a[-1] == "six" assert a[1, 3, 5] == ["two", "four", "six"] assert a.class == ArrayList
So are Maps...
def m = [one: 1, two: 2, three: 3] assert m["one"] == 1 assert m.values() as List == [1, 2, 3] assert m.getClass() == LinkedHashMap
Map/Bean Duality
Access beans as maps...
class Person { String firstName String lastName } Person person = new Person(firstName: "John", lastName: "Hurst") assert person["firstName"] == "John" assert person["lastName"] == "Hurst"
Access maps as beans
Map map = [firstName: "John", lastName: "Hurst"] assert map.firstName == "John" assert map.lastName == "Hurst"
Closures
Literal block of code...
def c = {println "Hello World"} c.call() c()
Can take arguments...
def doubler = {v -> 2 * v} assert doubler(6) == 12
Can be passed to and returned from methods...
def makeMultiplier(m) { return {v -> m * v} } def tripler = makeMultiplier(3) assert tripler(6) == 18
Closures: Common Uses
Iteration over collections...
collection.each {item -> // do something with each item ... }
Resource aquisition...
file.withReader {reader -> // do something with Reader ... } file.withWriter {writer -> // do something with Writer ... }
Closures: Common Uses
Provide default value for Map...
def m = [:].withDefault {[]} // default is empty list m["a"] << 1 m["b"] << 2 m["c"] << 3 m["a"] << 4 m["b"] << 5 m["a"] << 6 assert m == [ a: [1, 4, 6], b: [2, 5], c: [3] ]
Closures: Common Uses
Enables a "functional" style...
fibo = [:].withDefault {i -> if (i == 0 || i == 1) 1 else fibo[i - 2] + fibo[i - 1] } assert fibs[0] == 1 assert fibs[1] == 1 assert fibs[10] == 89 assert fibs.size() == 11
Regular Expressions
def pattern = ~/^ftp:\/\/(.+):(.+)@(.*)$/ assert pattern.class == java.util.regex.Pattern
Regular Expressions
def pattern = ~/^ftp:\/\/(.+):(.+)@(.*)$/ assert pattern.class == java.util.regex.Pattern def url = "ftp://anonymous:secret@ftp.codehaus.org" def user = "" def password = "" def host = "" (url =~ pattern).each {m -> user = m[1] password = m[2] host = m[3] } assert [user, password, host] == ["anonymous", "secret", "ftp.codehaus.org"]
Dynamic Language Features
Dynamically typed...
- "Duck typing"
Dynamic properties and methods...
- Add properties and methods at runtime
- Groovy Builders respond to structure
- Grails and other frameworks work magic
- Domain Specific Languages
Dynamic Language Features
def o = new Object() o.metaClass.foo = {it.toUpperCase()} assert o.foo("bar") == "BAR"
class Foo { List methodCalls = [] Object invokeMethod(String name, Object args) { methodCalls << name } }
def f = new Foo() f.foo() f.bar(1, 2, 3) assert f.methodCalls == ["foo", "bar"]
Groovy JDK
Methods added to standard JDK classes...
- http://groovy.codehaus.org/groovy-jdk/
- See org.codehaus.groovy.runtime.DefaultGroovyMethods
- More than 900 methods added to standard classes
- Collection
- File
- and more...
Groovy JDK: Collection
def v = ["one", "two", "three", "four", "five", "six"] v.each {println it}
Groovy JDK: Collection
def v = ["one", "two", "three", "four", "five", "six"] v.each {println it} assert v.collect {it.toUpperCase()} == ["ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX"] assert v.collect {it.size()} == [3, 3, 5, 4, 4, 3]
Groovy JDK: Collection
def v = ["one", "two", "three", "four", "five", "six"] v.each {println it} assert v.collect {it.toUpperCase()} == ["ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX"] assert v.collect {it.size()} == [3, 3, 5, 4, 4, 3] assert v*.size() == [3, 3, 5, 4, 4, 3]
Groovy JDK: Collection
def v = ["one", "two", "three", "four", "five", "six"] v.each {println it} assert v.collect {it.toUpperCase()} == ["ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX"] assert v.collect {it.size()} == [3, 3, 5, 4, 4, 3] assert v*.size() == [3, 3, 5, 4, 4, 3] assert v.findAll {it.size() % 2 == 0} == ["four", "five"]
Groovy JDK: Collection
def v = ["one", "two", "three", "four", "five", "six"] v.each {println it} assert v.collect {it.toUpperCase()} == ["ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX"] assert v.collect {it.size()} == [3, 3, 5, 4, 4, 3] assert v*.size() == [3, 3, 5, 4, 4, 3] assert v.findAll {it.size() % 2 == 0} == ["four", "five"] assert v*.size().sum() == 22 assert v*.size().min() == 3 assert v*.size().max() == 5
... over 70 methods added to Collection
Groovy JDK: File
file.withReader {r -> def line = r.readLine() while (line != null) { lines << line line = r.readLine() } }
Groovy JDK: File
file.withReader {r -> def line = r.readLine() while (line != null) { lines << line line = r.readLine() } } file.eachLine {line -> lines << line }
Groovy JDK: File
file.withReader {r -> def line = r.readLine() while (line != null) { lines << line line = r.readLine() } } file.eachLine {line -> lines << line } lines = file.readLines()
Groovy JDK: File
file.withReader {r -> def line = r.readLine() while (line != null) { lines << line line = r.readLine() } } file.eachLine {line -> lines << line } lines = file.readLines() String text = file.text
Groovy JDK: File
file.withReader {r -> def line = r.readLine() while (line != null) { lines << line line = r.readLine() } } file.eachLine {line -> lines << line } lines = file.readLines() String text = file.text new File("groovy-home.html").bytes = new URL("http://groovy.codehaus.org").bytes
... over 70 methods added to File
Groovy JDK: String
def s = "Groovy" assert s.center(10) == " Groovy " assert s.padLeft(10) == " Groovy" assert s.padRight(10) == "Groovy " assert s[4] == "v" assert s[2..4] == "oov" assert s[1,3,5] == "roy"
... over 90 methods added to String
Applications
- Database access with Groovy SQL
- Parsing XML with XmlParser
- Outputting XML with MarkupBuilder
- Parallelism with GPars
- Building with Gradle
- Application Example
Groovy SQL
- Create a Sql object with a DataSource
- Run queries, updates, transactions, etc
- Groovy takes care of resource management
- Also adds dynamic properties to ResultSet
Groovy SQL
import oracle.jdbc.pool.OracleDataSource import groovy.sql.Sql Sql db = new Sql( new OracleDataSource(URL: "jdbc:oracle:thin:birt/birt@XE") )
Groovy SQL
import oracle.jdbc.pool.OracleDataSource import groovy.sql.Sql Sql db = new Sql( new OracleDataSource(URL: "jdbc:oracle:thin:birt/birt@XE") ) def cust = db.firstRow( "SELECT * FROM Customers WHERE CustomerNumber = ?", 114 ) assert cust.customerName == "Australian Collectors, Co." assert cust.contactLastName == "Ferguson"
Groovy SQL
import oracle.jdbc.pool.OracleDataSource import groovy.sql.Sql Sql db = new Sql( new OracleDataSource(URL: "jdbc:oracle:thin:birt/birt@XE") ) def cust = db.firstRow( "SELECT * FROM Customers WHERE CustomerNumber = ?", 114 ) assert cust.customerName == "Australian Collectors, Co." assert cust.contactLastName == "Ferguson" def custs = db.rows("SELECT * FROM Customers") assert custs.size() == 122
Groovy SQL
import oracle.jdbc.pool.OracleDataSource import groovy.sql.Sql Sql db = new Sql( new OracleDataSource(URL: "jdbc:oracle:thin:birt/birt@XE") ) def cust = db.firstRow( "SELECT * FROM Customers WHERE CustomerNumber = ?", 114 ) assert cust.customerName == "Australian Collectors, Co." assert cust.contactLastName == "Ferguson" def custs = db.rows("SELECT * FROM Customers") assert custs.size() == 122 db.eachRow("SELECT * FROM Customers") {row -> if (row.customerNumber == 114) { println row.customerName } }
Parsing and Outputting XML
- Dynamic properties and methods for accessing XML
- XmlParser, also enhancements to DOM
- Dynamic MarkupBuilder for outputting XML
Parsing and Outputting XML
<Employees> <Employee EmployeeNumber='1165' LastName='Jennings' FirstName='Leslie' Extension='x3291' /> <Employee EmployeeNumber='1166' LastName='Thompson' FirstName='Leslie' Extension='x4065' /> <Employee EmployeeNumber='1188' LastName='Firrelli' FirstName='Julie' Extension='x2173' /> <Employee EmployeeNumber='1216' LastName='Patterson' FirstName='Steve' Extension='x4334' /> <Employee EmployeeNumber='1286' LastName='Tseng' FirstName='Foon Yue' Extension='x2248' /> <Employee EmployeeNumber='1323' LastName='Vanauf' FirstName='George' Extension='x4102' /> </Employees>
EmployeeNumber,LastName,FirstName,Extension 1165,Jennings,Leslie,x3291 1166,Thompson,Leslie,x4065 1188,Firrelli,Julie,x2173 1216,Patterson,Steve,x4334 1286,Tseng,Foon Yue,x2248 1323,Vanauf,George,x4102
Parsing XML
def employees = new XmlParser().parse(inputFile) outputFile.withWriter {writer -> writer << "EmployeeNumber,LastName,FirstName,Extension\n" employees.Employee.each {employee -> writer << employee.@EmployeeNumber << "," << employee.@LastName << "," << employee.@FirstName << "," << employee.@Extension << "\n" } }
Outputting XML
outputFile.withWriter {writer -> new MarkupBuilder(writer).Employees { inputFile.splitEachLine(",") {items -> if (items[0] != "EmployeeNumber") { Employee( EmployeeNumber: items[0], LastName: items[1], FirstName: items[2], Extension: items[3] ) } } } }
GPars Parallelism
- Natural, elegant syntax for common design patterns
- Support for Fork/Join
GPars Parallelism
def topics = [ "Groovy_(programming_language)", "Object-oriented_programming", "Programming_paradigm", "Paradigm", "Science", "Knowledge", "Fact", "Information", "Sequence", "Mathematics", "Quantity", "Property_(philosophy)", "Modern_philosophy", "Philosophy" ] def fetch = {topic -> def url = "http://en.wikipedia.org/wiki/$topic" def filename = topic.replaceAll("[()]", "") + ".html" println "Fetching $url..." new File(filename).bytes = new URL(url).bytes println "Saved $filename" } topics.each {topic -> fetch(topic) }
GPars Parallelism
import groovyx.gpars.GParsPool def topics = [ "Groovy_(programming_language)", "Object-oriented_programming", "Programming_paradigm", "Paradigm", "Science", "Knowledge", "Fact", "Information", "Sequence", "Mathematics", "Quantity", "Property_(philosophy)", "Modern_philosophy", "Philosophy" ] def fetch = {topic -> def url = "http://en.wikipedia.org/wiki/$topic" def filename = topic.replaceAll("[()]", "") + ".html" println "Fetching $url..." new File(filename).bytes = new URL(url).bytes println "Saved $filename" } GParsPool.withPool() { def fetchAsync = fetch.async() topics.each {topic -> fetchAsync(topic) } }
Gradle
- Convention with flexibility
- Extensible domain model for build project
- Support for any Ant task
- Uses Ivy/Maven repositories for dependencies
Gradle
apply plugin: "java"
Gradle
apply plugin: "java" repositories { mavenCentral() } dependencies { compile group: "commons-lang", name: "commons-lang", version: "2.5" compile group: "org.springframework", name: "spring-jdbc", version: "3.0.0.RELEASE" }
Gradle
apply plugin: "java" repositories { mavenCentral() } dependencies { compile group: "commons-lang", name: "commons-lang", version: "2.5" compile group: "org.springframework", name: "spring-jdbc", version: "3.0.0.RELEASE" testCompile group: "junit:junit:4.8.1" testCompile group: "org.dbunit:dbunit:2.4.7" testCompile group: "org.slf4j:slf4j-simple:1.5.6" }
Gradle
apply plugin: "java" repositories { mavenCentral() } dependencies { compile group: "commons-lang", name: "commons-lang", version: "2.5" compile group: "org.springframework", name: "spring-jdbc", version: "3.0.0.RELEASE" testCompile group: "junit:junit:4.8.1" testCompile group: "org.dbunit:dbunit:2.4.7" testCompile group: "org.slf4j:slf4j-simple:1.5.6" testCompile files("lib/system/ojdbc6-11.2.0.1.0.jar") }
Gradle
apply plugin: "java" repositories { mavenCentral() } dependencies { ... } test { systemProperties "jdbc.driver": "oracle.jdbc.OracleDriver" systemProperties "jdbc.url": "jdbc:oracle:thin:@oracle_on_vmware:1521:XE" systemProperties "jdbc.userid": "birt" systemProperties "jdbc.password": "birt" }
Gradle
apply plugin: "jetty" configurations { compileGwt.extendsFrom compile } dependencies { compileGwt group: "com.google.gwt:gwt-dev:2.2.0" compile group: "com.google.gwt:gwt-servlet:2.2.0" compile group: "com.google.gwt:gwt-user:2.2.0" }
Gradle
apply plugin: "jetty" configurations { compileGwt.extendsFrom compile } dependencies { ... } task gwtc << { ant.java( classname: "com.google.gwt.dev.Compiler", args: "-war build/gwt org.nzoug.sample.SampleApplication" ) { classpath { path(path: configurations.compileGwt.asPath) path(location: "src/main/java") } } } war.dependsOn(gwtc) war { from "build/gwt" }
@Grab
- Fetch dependencies in scripts
- Fetch from Maven Central by default
- Configure alternatives via Ivy
@Grab
@Grab("org.apache.poi:poi:3.7") import org.apache.poi.hssf.usermodel.HSSFWorkbook new File(args[0]).withInputStream {is -> def workbook = new HSSFWorkbook(is) 0.upto(workbook.sheetCount).each {i -> println workbook.getSheet(i).sheetName } }
A Worksheet Builder
def dt(v) {Date.parse("yyyy-MM-dd", v)} def worksheet = new HSSFWorkbookBuilder().workbook { sheet("Invoices") { row(["ID", "Date", "Amount"]) row(["1", dt("2011-08-31"), 1035.00]) row(["2", dt("2011-09-30"), 2520.00]) row(["3", dt("2011-10-31"), 350.00]) } sheet("Expenses") { row(["Date", "Amount"]) row([dt("2011-08-31"), 46.38]) row([dt("2011-09-30"), 99.45]) } } assert worksheet.getSheet("Expenses") .getRow(1).getCell(1).numericCellValue == 46.38
A Worksheet Builder
class HSSFWorkbookBuilder { private Workbook workbook = new HSSFWorkbook() private Sheet sheet private int rows Workbook workbook(Closure closure) { closure.delegate = this closure.call() workbook } void sheet(String name, Closure closure) { sheet = workbook.createSheet(name) rows = 0 closure.delegate = this closure.call() } void row(values) { Row row = sheet.createRow(rows++ as int) values.eachWithIndex {value, col -> Cell cell = row.createCell(col) switch (value) { case Date: cell.setCellValue((Date) value); break case Double: cell.setCellValue((Double) value); break case BigDecimal: cell.setCellValue(((BigDecimal) value).doubleValue()); break default: cell.setCellValue(new HSSFRichTextString("" + value)); break } } } }
A Worksheet Builder
String connectionString = args[0] String sql = new File(args[1]).text File xlsFile = new File(args[2]) Sql db = new Sql( new OracleDataSource(URL: "jdbc:oracle:thin:${connectionString}") ) xlsFile.withOutputStream {os -> def workbook = new HSSFWorkbookBuilder().workbook { sheet("Data") { db.eachRow( sql, {meta -> row(meta*.columnName)}, {rs -> row(rs.toRowResult().values())} ) } } workbook.write(os) }
XLS Result

Groovy Ecosystem
- easyb - Behaviour Driven Development
- Geb - Very Groovy Browser Automation
- GPars - Groovy Parallel Systems
- Gradle - "A better way to build"
- Grails - "The Search is Over"
- Spock - Testing and Specification Framework
Groovy
The Dynamic Language for the JVM
- John Hurst
- john.hurst@skepticalhumorist.co.nz
- http://www.skepticalhumorist.co.nz
- http://skepticalhumorist.blogspot.com/
- @skepticalhumor
/