Groovy Update, new in 1.8 and beyond - Guillaume Laforge - Devoxx 2011
1. What’s new in Groovy?
Groovy 1.8 and what’s ahead
Guillaume Laforge @glaforge
Groovy Project Manager http://glaforge.appspot.com
SpringSource / VMware http://gplus.to/glaforge
2. Guillaume Laforge
• Groovy Project Manager at VMware
• Initiator of the Grails framework
• Creator of the Gaelyk toolkit
• Co-author of Groovy in Action
• Follow me on...
• My blog: http://glaforge.appspot.com
• Twitter: @glaforge
• Google+: http://gplus.to/glaforge
2
3. Agenda
• What’s new in Groovy 1.8?
–Nicer DSLs with command chains
–Runtime performance improvements
–GPars bundled for taming your multicores
–Closure enhancements
–Builtin JSON support
–New AST transformations
3
4. Agenda
• What’s cooking for Groovy 2.0?
–Alignments with JDK 7
• Project Coin (small language changes)
• InvokeDynamic
– Continued runtime performance improvements
–Static type checking and compilation
4
5. Command chains
• A grammar improvement allowing you
to drop dots & parens
when chaining method calls
–an extended version of top-level statements like println
• Less dots, less parens allow you to
–write more readable business rules
–in almost plain English sentences
• (or any language, of course)
5
12. Command chains
Before... we used to do...
take 2.pills, of: chloroquinine, after: 6.hours
8
13. Command chains
Before... we used to do...
take 2.pills, of: chloroquinine, after: 6.hours
Normal argument
8
14. Command chains
Before... we used to do...
take 2.pills, of: chloroquinine, after: 6.hours
Normal argument Named arguments
8
15. Command chains
Before... we used to do...
take 2.pills, of: chloroquinine, after: 6.hours
Normal argument Named arguments
Woud call:
def take(Map m, Quantity q)
8
16. Command chains
Now, even less punctuation!
take 2 pills of chloroquinine after 6 hours
9
17. Command chains
Now, even less punctuation!
take).2 pills of chloroquinine )
( ( ). ( ). ( after 6 hours
9
18. Command chains
// Java fluent API approach
class RegimenBuilder {
...
def take(int n) {
this.pills = n
return this
}
def pills(String of) {
return this
}
...
}
10
19. Command chains
// variable injection
def (of, after, hours) = /*...*/
// implementing the DSL logic
{ }
def take(n) {
[pills: { of ->
[chloroquinine: { after ->
['6': { time -> }]
}]
}]
}
//
----------------------------------------
take 2 pills of chloroquinine after 6 hours
11
22. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
12
23. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
// leverage named-args as punctuation
12
24. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
// leverage named-args as punctuation
check that: margarita tastes good
12
25. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
// leverage named-args as punctuation
check that: margarita tastes good
// closure parameters for new control structures
12
26. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
// leverage named-args as punctuation
check that: margarita tastes good
// closure parameters for new control structures
given {} when {} then {}
12
27. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
// leverage named-args as punctuation
check that: margarita tastes good
// closure parameters for new control structures
given {} when {} then {}
// zero-arg methods require parens
12
28. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
// leverage named-args as punctuation
check that: margarita tastes good
// closure parameters for new control structures
given {} when {} then {}
// zero-arg methods require parens
select all unique() from names
12
29. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
// leverage named-args as punctuation
check that: margarita tastes good
// closure parameters for new control structures
given {} when {} then {}
// zero-arg methods require parens
select all unique() from names
// possible with an odd number of terms
12
30. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
// leverage named-args as punctuation
check that: margarita tastes good
// closure parameters for new control structures
given {} when {} then {}
// zero-arg methods require parens
select all unique() from names
// possible with an odd number of terms
take 3 cookies
12
31. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
( ). ( ). ( )
// leverage named-args as punctuation
check that: margarita tastes good
// closure parameters for new control structures
given {} when {} then {}
// zero-arg methods require parens
select all unique() from names
// possible with an odd number of terms
take 3 cookies
12
32. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
( ). ( ). ( )
// leverage named-args as punctuation
check that: margarita )tastes good
( ). (
// closure parameters for new control structures
given {} when {} then {}
// zero-arg methods require parens
select all unique() from names
// possible with an odd number of terms
take 3 cookies
12
33. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
( ). ( ). ( )
// leverage named-args as punctuation
check that: margarita )tastes good
( ). (
// closure parameters for new control structures
given). when {} )
( {} ( ). ( then {}
// zero-arg methods require parens
select all unique() from names
// possible with an odd number of terms
take 3 cookies
12
34. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
( ). ( ). ( )
// leverage named-args as punctuation
check that: margarita )tastes good
( ). (
// closure parameters for new control structures
given). when {} )
( {} ( ). ( then {}
// zero-arg methods require parens
select all unique() from names
( ). . ( )
// possible with an odd number of terms
take 3 cookies
12
35. Command chains
// methods with multiple arguments (commas)
take coffee with sugar, milk and liquor
( ). ( ). ( )
// leverage named-args as punctuation
check that: margarita )tastes good
( ). (
// closure parameters for new control structures
given). when {} )
( {} ( ). ( then {}
// zero-arg methods require parens
select all unique() from names
( ). . ( )
// possible with an odd number of terms
take).3 cookies
(
12
36. Runtime performance
• Significant runtime improvements
for primitive type operations
–classical Fibonacci example x13 faster!
–closer to Java performance
• Some direct method calls on this
13
37. GPars bundled
• GPars is bundled
in the Groovy distribution
• GPars covers a wide range of parallel and
concurrent paradigms
–actors, fork/join, map/filter/reduce, dataflow, agents
–parallel arrays, executors, STM, and more...
14
38. Closure enhancements
• Closure annotation parameters
• Some more functional flavor
–composition
–trampoline
–memoization
• Currying improvements
15
39. Closure annotation parameters
@Retention(RetentionPolicy.RUNTIME)
@interface Invariant {
Class value() // a closure class
}
{
@Invariant({ number >= 0 })
class Distance {
float number
String unit
}
def d = new Distance(number: 10, unit: "meters")
def anno = Distance.getAnnotation(Invariant)
def check = anno.value().newInstance(d, d)
assert check(d)
16
40. Closure annotation parameters
@Retention(RetentionPolicy.RUNTIME)
@interface Invariant {
Class value() // a closure class
}
{
@Invariant({ number >= 0 })
class Distance {
GCon tracts
float number oor-m an’s
P
String unit
}
def d = new Distance(number: 10, unit: "meters")
def anno = Distance.getAnnotation(Invariant)
def check = anno.value().newInstance(d, d)
assert check(d)
16
67. @Log
• Four different loggers can be injected
–@Log, @Commons, @Log4j, @Slf4j
• Possible to implement your own strategy
import groovy.util.logging.*
@Log
class Car {
Car() {
log.info 'Car constructed'
}
}
def c = new Car()
26
68. @Log
• Four different loggers can be injected
–@Log, @Commons, @Log4j, @Slf4j
• Possible to implement your own strategy
import groovy.util.logging.*
@Log
class Car {
Guarded Car() {
w/ an if log.info 'Car constructed'
}
}
def c = new Car()
26
69. @Field
• Surprising scoping rules in scripts
–variables are local to the run() method
–variables aren’t visible in methods
• although visually the variable is in a surrounding scope
• @Field creates a field in the script class
@Field List awe = [1, 2, 3]
def awesum() { awe.sum() }
assert awesum() == 6
27
70. Controlling code execution
• Your application may run user’s code
–what if the code runs in infinite loops or for too long?
–what if the code consumes too many resources?
28
71. Controlling code execution
• Your application may run user’s code
–what if the code runs in infinite loops or for too long?
–what if the code consumes too many resources?
• 3 new transforms at your rescue
–@ThreadInterrupt: adds Thread#isInterrupted checks
so your executing thread stops when interrupted
–@TimedInterrupt: adds checks in method and closure
bodies to verify it’s run longer than expected
–@ConditionalInterrupt: adds checks with your own
conditional logic to break out from the user code
28
72. Controlling code execution
• Your application may run user’s code
–what if the code runs in infinite loops or for too long?
–what if the code consumes too many resources?
• 3 new transforms at your rescue
–@ThreadInterrupt: adds Thread#isInterrupted checks
so your executing thread stops when interrupted
–@TimedInterrupt: adds checks in method and closure
bodies to verify it’s run longer than expected
–@ConditionalInterrupt: adds checks with your own
conditional logic to break out from the user code
• Also see compilation customizers later on 28
73. @ThreadInterrupt
@ThreadInterrupt
import groovy.transform.ThreadInterrupt
while (true) {
// eat lots of CPU
}
29
33
74. @ThreadInterrupt
@ThreadInterrupt
import groovy.transform.ThreadInterrupt
while (true) {
{ if (Thread.currentThread.isInterrupted())
throw new InterruptedException() }
// eat lots of CPU
}
29
33
75. @ThreadInterrupt
@ThreadInterrupt
import groovy.transform.ThreadInterrupt
while (true) {
{ if (Thread.currentThread.isInterrupted())
throw new InterruptedException() }
// eat lots of CPU
}
• Two optional annotation parameters available
–checkOnMethodStart (true by default)
–applyToAllClasses (true by default)
29
33
76. @TimedInterrupt
@TimedInterrupt(10)
import groovy.transform.TimedInterrupt
while (true) {
// eat lots of CPU
}
• InterruptedException thrown when checks
indicate code ran longer than desired
• Optional annotation parameters available
–same as @ThreadInterrupt
–value: for an amount of time duration
–unit: for the time duration unit (TimeUnit.SECONDS) 30
77. @ConditionalInterrupt
• Specify your own condition to be inserted
at the start of method and closure bodies
– check for available resources, number of times run, etc.
• Leverages closure annotation parameters from
Groovy 1.8
@ConditionalInterrupt({ counter++ > 2 })
import groovy.transform.ConditionalInterrupt
import groovy.transform.Field
@Field int counter = 0
100.times {
println 'executing script method...'
}
31
78. @ToString
• Provides a default toString() method to your types
• Available annotation options
– includeNames, includeFields, includeSuper, excludes
import groovy.transform.ToString
@ToString
class Person {
String name
int age
}
println new Person(name: 'Pete', age: 15)
// => Person(Pete, 15)
32
79. @EqualsAndHashCode
• Provides default implementations for equals()
and hashCode() methods
import groovy.transform.EqualsAndHashCode
@EqualsAndHashCode
class Coord {
int x, y
}
def c1 = new Coord(x: 20, y: 5)
def c2 = new Coord(x: 20, y: 5)
assert c1 == c2
assert c1.hashCode() == c2.hashCode()
33
80. @TupleConstructor
• Provides a «classical» constructor with all
properties
• Several annotation parameter options available
import groovy.transform.TupleConstructor
@TupleConstructor
class Person {
String name
int age
}
def m = new Person('Marion', 3)
assert m.name == 'Marion'
assert m.age == 3
34
81. @Canonical
• One annotation to rule them all!
–@Canonical mixes together
• @ToString
• @EqualsAndHashCode
• @TupleConstructor
• You can customize behavior by combining
@Canonical and one of the other annotations
35
82. @InheritConstructors
• Classes like Exception are painful when
extended, as all the base constructors should
be replicated
class CustomException extends Exception {
CustomException() { super() }
CustomException(String msg) { super(msg) }
CustomException(String msg, Throwable t) { super(msg, t) }
CustomException(Throwable t) { super(t) }
}
36
83. @InheritConstructors
• Classes like Exception are painful when
extended, as all the base constructors should
be replicated
import groovy.transform.*
@InheritConstructors
class CustomException extends Exception {
CustomException() { super() }
CustomException(String msg) { super(msg) }
CustomException(String msg, Throwable t) { super(msg, t) }
CustomException(Throwable t) { super(t) }
}
36
84. @WithReadLock, @WithWriteLock
import groovy.transform.*
class ResourceProvider {
private final Map<String, String> data = new HashMap<>()
@WithReadLock
String getResource(String key) {
return data.get(key)
}
@WithWriteLock
void refresh() {
//reload the resources into memory
}
}
37
85. @WithReadLock, @WithWriteLock
import groovy.transform.*
class ResourceProvider {
private final Map<String, String> data = new HashMap<>()
{ @WithReadLock
String getResource(String key) {
return data.get(key)
}
{ @WithWriteLock
void refresh() {
//reload the resources into memory
}
}
37
87. Compilation customizers
• Ability to apply some customization to the Groovy
compilation process
• Three available customizers
–ImportCustomizer
–ASTTransformationCustomizer
–SecureASTCustomizer
• But you can implement your own
39
88. Imports customizer
def configuration = new CompilerConfiguration()
def custo = new ImportCustomizer()
custo.addStaticStar(Math.name)
configuration.addCompilationCustomizers(custo)
def result = new GroovyShell(configuration)
.evaluate(" cos PI/3 ")
40
89. Applying an AST transformation
def configuration = new CompilerConfiguration()
configuration.addCompilationCustomizers(
new ASTTransformationCustomizer(Log))
new GroovyShell(configuration).evaluate("""
class Car {
Car() {
log.info 'Car constructed'
}
}
log.info 'Constructing a car'
def c = new Car()
""")
41
90. Secure AST customizer
Idea: Implement an «arithmetic shell»
Being able control what a user script is
allowed to do: only arithmetic expressions
• Let’s setup our environment
–some imports
–an import customizer to import java.lang.Math.*
–prepare a secure AST customizer
import org.codehaus.groovy.control.customizers.*
import org.codehaus.groovy.control.*
import static org.codehaus.groovy.syntax.Types.*
def imports = new ImportCustomizer().addStaticStars('java.lang.Math')
def secure = new SecureASTCustomizer()
42
92. Secure AST customizer
...
// language tokens allowed
tokensWhitelist = [
PLUS, MINUS, MULTIPLY, DIVIDE, MOD, POWER, PLUS_PLUS,
MINUS_MINUS, COMPARE_EQUAL, COMPARE_NOT_EQUAL,
COMPARE_LESS_THAN, COMPARE_LESS_THAN_EQUAL,
COMPARE_GREATER_THAN, COMPARE_GREATER_THAN_EQUAL
]
// types allowed to be used (including primitive types)
constantTypesClassesWhiteList = [
Integer, Float, Long, Double, BigDecimal,
Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE
]
// classes who are allowed to be receivers of method calls
receiversClassesWhiteList = [
Math, Integer, Float, Double, Long, BigDecimal ]
}
...
44
93. Secure AST customizer
• Ready to evaluate our arithmetic expressions!
...
def config = new CompilerConfiguration()
config.addCompilationCustomizers(imports, secure)
def shell = new GroovyShell(config)
shell.evaluate 'cos PI/3'
• But the following would have failed:
shell.evaluate 'System.exit(0)'
45
94. Slashy and dollar slashy strings
• Slashy strings are now multiline
–particularly handy for multi-line regexs
• Dollar slashy string notation introduced
–a multiline GString with different escaping rules
def (name, date) = ["Guillaume", "Oct, 26th"]
def dollarSlashy = $/
Hello $name, today we're ${date}
$ dollar-sign
$$ dollar-sign
backslash
/ slash
$/ slash
/$
46
95. New GDK methods
// count
assert [2,4,2,1,3,5,2,4,3].count { it % 2 == 0 } == 5
// count by
assert [0:2, 1:3] == [1,2,3,4,5].countBy{ it % 2 }
assert [(true):2, (false):4]
== 'Groovy'.toList().countBy{ it == 'o' }
// min/max for maps with closures
def map = [a: 1, bbb: 4, cc: 5, dddd: 2]
assert map.max { it.key.size() }.key == 'dddd'
assert map.min { it.value }.value == 1
// map withDefault()
def words = "one two two three three three".split()
def freq = [:].withDefault { k -> 0 }
words.each { freq[it] += 1 }
47
96. (G)String to Enum coercion
enum Color {
red, green, blue
}
// coercion with as
def r = "red" as Color
// implicit coercion
Color b = "blue"
// with GStrings too
def g = "${'green'}" as Color
48
98. Launching remote scripts
• With the Groovy command, you can launch
remote scripts
> groovy http://groovyconsole.appspot.com/raw/110001
• Be careful to what
you’re executing :-)
50
100. Groovy 2.0 roadmap
• Java 7 alignements: Project Coin
–binary literals
–underscore in literals
–multicatch
• JDK 7: InvokeDynamic
• Towards a more modular Groovy
• Static type checking and compilation
• Miscelanous
• What else? Your call!
52
101. Groovy 2.0 roadmap
• Java 7 alignements: Project Coin
–binary literals
–underscore in literals
–multicatch
• JDK 7: InvokeDynamic
• Towards a more modular Groovy
• Static type checking and compilation
• Miscelanous
• What else? Your call!
52
103. Binary literals
• We had decimal, octal and hexadecimal
notations for number literals
• We can now use binary representations too
int x = 0b10101111
assert x == 175
byte aByte = 0b00100001
assert aByte == 33
int anInt = 0b1010000101000101
assert anInt == 41285
54
104. Underscore in literals
• Now we can also add underscores
in number literals for more readability
long creditCardNumber = 1234_5678_9012_3456L
long socialSecurityNumbers = 999_99_9999L
float monetaryAmount = 12_345_132.12
long hexBytes = 0xFF_EC_DE_5E
long hexWords = 0xFFEC_DE5E
long maxLong = 0x7fff_ffff_ffff_ffffL
long alsoMaxLong = 9_223_372_036_854_775_807L
long bytes = 0b11010010_01101001_10010100_10010010
55
105. Multicatch
• One block for multiple exception caught
–rather than duplicating the block
try {
/* ... */
} catch(IOException | NullPointerException e) {
/* one block to treat 2 exceptions */
}
56
106. InvokeDynamic
• Groovy 2.0 will support JDK 7’s
invokeDynamic
–an «indy» branch started recently
–compiler will have a flag for compiling against JDK 7
–might use the invokeDynamic backport
• Benefits
–more runtime performance!
–in the long run, will allow us to get rid of code!
• call site caching, thanks to MethodHandles
• metaclass registry, thanks to ClassValues
• will let the JIT inline calls more easily 57
107. Groovy Modularity
• Groovy’s «all» JAR weighs in at 4MB
• Nobody needs everything
–Template engine, Ant scripting, Swing UI building...
• Provide a smaller core
–and several smaller JARs per feature
• Provide hooks for setting up EMCs / DGM
methods, etc.
58
108. Static Type Checking
• Goal: make the Groovy compiler «grumpy»!
–and throw compilation errors (not at runtime)
• Not everybody needs dynamic features all the time
–think Java libraries scripting
• Grumpy should...
–tell you about your method or variable typos
–complain if you call methods that don’t exist
–shout on assignments of wrong types
–infer the types of your variables
–figure out GDK methods
–etc...
59
109. Typos in your variable or method
import groovy.transform.StaticTypes
void method() {}
@TypeChecked test() {
// Cannot find matching method metthhoood()
metthhoood()
def name = "Guillaume"
// variable naamme is undeclared
println naamme
}
60
110. Typos in your variable or method
import groovy.transform.StaticTypes
void method() {}
@TypeChecked test() {
// Cannot find matching method metthhoood()
metthhoood()
def name = "Guillaume" Compilation
// variable naamme is undeclared errors!
println naamme
}
60
111. Complain on wrong assignments
// cannot assign value of type... to variable...
int x = new Object()
Set set = new Object()
def o = new Object()
int x = o
String[] strings = ['a','b','c']
int str = strings[0]
// cannot find matching method plus()
int i = 0
i += '1'
61
112. Complain on wrong assignments
// cannot assign value of type... to variable...
int x = new Object()
Set set = new Object() Compilation
errors!
def o = new Object()
int x = o
String[] strings = ['a','b','c']
int str = strings[0]
// cannot find matching method plus()
int i = 0
i += '1'
61
113. Complain on wrong return types
String method() { 'String' }
// cannot assign value of type String...
int x = method()
// checks if/else branch return values
int method() {
if (true) { 'String' }
else { 42 }
}
// works for switch/case & try/catch/finally
// transparent toString() implied
String greeting(String name) {
def sb = new StringBuilder()
sb << "Hi" << name
}
62
114. Complain on wrong return types
String method() { 'String' }
// cannot assign value of type String...
int x = method() Compilation
errors!
// checks if/else branch return values
int method() {
if (true) { 'String' }
else { 42 }
}
// works for switch/case & try/catch/finally
// transparent toString() implied
String greeting(String name) {
def sb = new StringBuilder()
sb << "Hi" << name
}
62
115. Type inference
@TypeChecked test() {
def name = " Guillaume "
// String type infered (even inside GString)
println "NAME = ${name.toUpperCase()}"
// Groovy GDK method support
// (GDK operator overloading too)
println name.trim()
int[] numbers = [1, 2, 3]
// Element n is an int
for (int n in numbers) {
println n
}
}
63
116. Staticly checked & dyn. methods
@TypeChecked
String greeting(String name) {
// call method with dynamic behavior
// but with proper signature
generateMarkup(name.toUpperCase())
}
// usual dynamic behavior
String generateMarkup(String name) {
def sw = new StringWriter()
new MarkupBuilder(sw).html {
body {
div name
}
}
sw.toString()
}
64
117. Bytecode viewer
• Groovy Console’s
bytecode viewer
from the AST
browser window
–select class
generation phase
–select a class node
65
118. Disable Global AST xforms
// witout a specific configuration, the AST builder works
def shell = new GroovyShell()
shell.evaluate(script) // passes
def config = new CompilerConfiguration()
config.disabledGlobalASTTransformations =
['org.codehaus.groovy.ast.builder.AstBuilderTransformation']
def script = '''
import org.codehaus.groovy.ast.builder.AstBuilder
new AstBuilder().buildFromCode { "Hello" }
'''
// now with the configuration in place,
// the AST builder transform is not applied
shell = new GroovyShell(config) // <-- with the configuration
shouldFail {
shell.evaluate(script) // fails
}
66
120. Thank you!
e
aforg pment
ume L Develo
Guilla Groovy
f
Head o om
e@ gmail.c
glaforg rge
Email: @glafo e
o/glaforg
Tw itter : ://gplus.t pot.com
+: http rge.apps
Google p://glafo
B log: htt
68