Slide 1

Slide 1 text

Sinon quoi de neuf côté Apache Groovy? Guillaume Laforge
 @glaforge 
 PMC Chair of 
 Apache Groovy 
 
 Developer Advocate 
 Google Cloud Platform Cédric Champeau
 @cedricchampeau 
 PMC Member of 
 Apache Groovy 
 
 Principal Engineer Gradle Inc.

Slide 2

Slide 2 text

2 @Canonical becomes a meta-annotation import groovy.transform.*
 
 @Canonical(includeNames = true)
 class Person {
 String name
 int age
 }
 
 assert new Person('Guillaume', 39).toString() ==
 'Person(name:Guillaume, age:39)'

Slide 3

Slide 3 text

2 @Canonical becomes a meta-annotation import groovy.transform.*
 
 @Canonical(includeNames = true)
 class Person {
 String name
 int age
 }
 
 assert new Person('Guillaume', 39).toString() ==
 'Person(name:Guillaume, age:39)' includeNames from @ToString

Slide 4

Slide 4 text

3 More control on annotation collector DUPLICATE Annotations from the annotation collection will always be inserted. PREFER_COLLECTOR Annotations from the collector will be added and any existing annotations with the same name will be removed. PREFER_EXPLICIT Annotations from the collector will be ignored if any existing annotations with the same name are found. PREFER_EXPLICIT_MERGED Annotations from the collector will be ignored if any existing annotations with the same name are found but any new parameters on the collector annotation will be added to existing annotations. PREFER_COLLECTOR_MERGED Annotations from the collector will be added and any existing annotations with the same name will be removed but any new parameters found within existing annotations will be merged into the added annotation.

Slide 5

Slide 5 text

4 Pre/post events for @TupleConstructor import groovy.transform.TupleConstructor @TupleConstructor(pre = { first = first?.toLowerCase() }) class Person { String first } def p = new Person('Jack') assert p.first == 'jack'

Slide 6

Slide 6 text

4 Pre/post events for @TupleConstructor import groovy.transform.TupleConstructor @TupleConstructor(pre = { first = first?.toLowerCase() }) class Person { String first } def p = new Person('Jack') assert p.first == 'jack' import groovy.transform.TupleConstructor import static groovy.test.GroovyAssert.shouldFail @TupleConstructor(pre = { assert first }) class Person { String first } def p = new Person('Jack') shouldFail { def unknown = new Person() }

Slide 7

Slide 7 text

5 Prevent @TupleConstructor default ctors @TupleConstructor
 class Person {
 String first, last
 int age
 }

Slide 8

Slide 8 text

5 Prevent @TupleConstructor default ctors @TupleConstructor
 class Person {
 String first, last
 int age
 } Generates: Person(String first, String last, int age) { /*...*/ } Person(String first, String last) { this(first, last, 0) } Person(String first) { this(first, null) } Person() { this(null) }

Slide 9

Slide 9 text

6 Prevent @TupleConstructor default ctors @TupleConstructor(defaults = true)
 class Person { String first, last
 int age
 }

Slide 10

Slide 10 text

6 Prevent @TupleConstructor default ctors @TupleConstructor(defaults = true)
 class Person { String first, last
 int age
 } Generates only: Person(String first, String last, int age) { /*...*/ }

Slide 11

Slide 11 text

7 New @MapConstructor transformation import groovy.transform.*
 
 @TupleConstructor
 class Person {
 String first, last
 }
 
 @CompileStatic // optional
 @ToString(includeSuperProperties = true)
 @MapConstructor(pre = { super(args?.first, args?.last);
 args = args ?: [:] },
 post = { first = first?.toUpperCase() })
 class Author extends Person {
 String bookName
 } assert new Author(first: 'Dierk', last: 'Koenig', bookName: 'ReGinA').toString() == 'Author(ReGinA, DIERK, Koenig)'
 
 assert new Author().toString() == 'Author(null, null, null)'

Slide 12

Slide 12 text

7 New @MapConstructor transformation import groovy.transform.*
 
 @TupleConstructor
 class Person {
 String first, last
 }
 
 @CompileStatic // optional
 @ToString(includeSuperProperties = true)
 @MapConstructor(pre = { super(args?.first, args?.last);
 args = args ?: [:] },
 post = { first = first?.toUpperCase() })
 class Author extends Person {
 String bookName
 } assert new Author(first: 'Dierk', last: 'Koenig', bookName: 'ReGinA').toString() == 'Author(ReGinA, DIERK, Koenig)'
 
 assert new Author().toString() == 'Author(null, null, null)' Can decorate map ctor with pre / post- instructions

Slide 13

Slide 13 text

8 Properties validated in AST xforms import groovy.transform.AutoClone
 
 @AutoClone(excludes = 'sirName')
 class Person {
 String firstName
 String surName
 }
 
 new Person(firstName: "John", surName: "Doe").clone()

Slide 14

Slide 14 text

8 Properties validated in AST xforms import groovy.transform.AutoClone
 
 @AutoClone(excludes = 'sirName')
 class Person {
 String firstName
 String surName
 }
 
 new Person(firstName: "John", surName: "Doe").clone() Error during @AutoClone processing: 'excludes' property 'sirName' does not exist.

Slide 15

Slide 15 text

9 @Immutable support in class hierarchy import groovy.transform.*
 
 @EqualsAndHashCode
 class Person {
 String name
 }

Slide 16

Slide 16 text

9 @Immutable support in class hierarchy import groovy.transform.*
 
 @EqualsAndHashCode
 class Person {
 String name
 } @Immutable
 @TupleConstructor(includeSuperProperties = true)
 @EqualsAndHashCode(callSuper = true)
 @ToString(includeNames = true, includeSuperProperties = true)
 class Athlete extends Person {
 String sport
 }

Slide 17

Slide 17 text

9 @Immutable support in class hierarchy import groovy.transform.*
 
 @EqualsAndHashCode
 class Person {
 String name
 } @Immutable
 @TupleConstructor(includeSuperProperties = true)
 @EqualsAndHashCode(callSuper = true)
 @ToString(includeNames = true, includeSuperProperties = true)
 class Athlete extends Person {
 String sport
 } def d1 = new Athlete('Michael Jordan', 'BasketBall')
 def d2 = new Athlete(name: 'Roger Federer', sport: 'Tennis') assert d1 != d2
 assert d1.toString() == 
 'Athlete(sport:BasketBall, name:Michael Jordan)'
 assert d2.toString() == 
 'Athlete(sport:Tennis, name:Roger Federer)'

Slide 18

Slide 18 text

10 @Immutable supports Optional import groovy.transform.Immutable
 
 @Immutable
 class Person {
 String name
 Optional address
 }
 
 def p = new Person('Joe', Optional.of('Home'))
 
 assert p.toString() == 'Person(Joe, Optional[Home])'
 assert p.address.get() == 'Home'

Slide 19

Slide 19 text

10 @Immutable supports Optional import groovy.transform.Immutable
 
 @Immutable
 class Person {
 String name
 Optional address
 }
 
 def p = new Person('Joe', Optional.of('Home'))
 
 assert p.toString() == 'Person(Joe, Optional[Home])'
 assert p.address.get() == 'Home' @Immutable
 class Person {
 String name
 Optional birth
 } Fails compilation, as Date is mutable

Slide 20

Slide 20 text

11 New @AutoImplement transformation @AutoImplement
 class MyNames extends AbstractList implements Closeable {}

Slide 21

Slide 21 text

11 New @AutoImplement transformation @AutoImplement
 class MyNames extends AbstractList implements Closeable {} class MyNames extends AbstractList implements Closeable {
 String get(int param0) {
 return null
 }
 boolean addAll(Collection extends String> param0) {
 return false
 }
 void close() throws Exception {
 }
 int size() {
 return 0
 }
 }

Slide 22

Slide 22 text

12 New @AutoImplement transformation @AutoImplement(exception = IOException)
 class MyWriter extends Writer { }

Slide 23

Slide 23 text

12 New @AutoImplement transformation @AutoImplement(exception = IOException)
 class MyWriter extends Writer { } @AutoImplement(exception = UnsupportedOperationException,
 message = 'Not supported by MyIterator')
 class MyIterator implements Iterator { }

Slide 24

Slide 24 text

12 New @AutoImplement transformation @AutoImplement(exception = IOException)
 class MyWriter extends Writer { } @AutoImplement(exception = UnsupportedOperationException,
 message = 'Not supported by MyIterator')
 class MyIterator implements Iterator { } @AutoImplement(code = { throw new UnsupportedOperationException(
 'Should never be called but was called on ' + new Date()) })
 class EmptyIterator implements Iterator {
 boolean hasNext() { false }
 }

Slide 25

Slide 25 text

13 :grab command in groovysh groovy:000> :grab 'com.google.guava:guava:19.0' groovy:000> import com.google.common.collect.BiMap ===> com.google.common.collect.BiMap

Slide 26

Slide 26 text

14 @Delegate on getters too class Person {
 String name @Delegate
 String getName() { name.reverse() } } def p = new Person(name: 'Erine') assert p.toUpperCase() == 'ENIRE'

Slide 27

Slide 27 text

15 JAXB marshalling shortcuts import groovy.transform.EqualsAndHashCode import javax.xml.bind.JAXBContext import javax.xml.bind.annotation.* @EqualsAndHashCode @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement class Person { String name int age }

Slide 28

Slide 28 text

15 JAXB marshalling shortcuts import groovy.transform.EqualsAndHashCode import javax.xml.bind.JAXBContext import javax.xml.bind.annotation.* @EqualsAndHashCode @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement class Person { String name int age } def jaxbContext = JAXBContext.newInstance(Person) def p = new Person(name: 'Marion', age: 9) def xml = jaxbContext.marshal(p) assert jaxbContext.unmarshal(xml, Person) == p

Slide 29

Slide 29 text

15 JAXB marshalling shortcuts import groovy.transform.EqualsAndHashCode import javax.xml.bind.JAXBContext import javax.xml.bind.annotation.* @EqualsAndHashCode @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement class Person { String name int age } def jaxbContext = JAXBContext.newInstance(Person) def p = new Person(name: 'Marion', age: 9) def xml = jaxbContext.marshal(p) assert jaxbContext.unmarshal(xml, Person) == p No need for createMarshaller()

Slide 30

Slide 30 text

16 CliBuilder supports annotations // specify parameters
 def cli = new CliBuilder(usage: 'groovy Greeter [option]')
 cli.a(longOpt: 'audience', args: 1, 'greeting audience')
 cli.h(longOpt: 'help', 'display usage')
 
 // parse and process parameters
 def options = cli.parse(args)
 if (options.h) cli.usage()
 else println "Hello ${options.a ? options.a : 'World'}"

Slide 31

Slide 31 text

17 CliBuilder supports annotations import groovy.cli.*
 
 interface GreeterI {
 @Option(shortName='h', description='display usage') Boolean help()
 @Option(shortName='a', description='greeting audience') String audience()
 @Unparsed List remaining()
 }

Slide 32

Slide 32 text

17 CliBuilder supports annotations import groovy.cli.*
 
 interface GreeterI {
 @Option(shortName='h', description='display usage') Boolean help()
 @Option(shortName='a', description='greeting audience') String audience()
 @Unparsed List remaining()
 } def cli = new CliBuilder(usage: 'groovy Greeter [option]')
 def argz = '--audience Groovologist'.split()
 def options = cli.parseFromSpec(GreeterI, argz)
 assert options.audience() == 'Groovologist'

Slide 33

Slide 33 text

18 With… tap… @Canonical
 class Person {
 String name
 int age
 }

Slide 34

Slide 34 text

18 With… tap… assert new Person().with { name = 'Guillaume' age = 39 return it }.toString() == 'Person(Guillaume, 39)' @Canonical
 class Person {
 String name
 int age
 }

Slide 35

Slide 35 text

18 With… tap… assert new Person().with { name = 'Guillaume' age = 39 return it }.toString() == 'Person(Guillaume, 39)' assert new Person().tap { name = 'Guillaume' age = 39 }.toString() == 'Person(Guillaume, 39)' @Canonical
 class Person {
 String name
 int age
 }

Slide 36

Slide 36 text

19 Miscellaneous • Map#retainAll {} and Map#removeAll {} methods • GDK’s createSimilarCollection() and createSimilarMap() methods support all the JDK’s collections and maps • New File#relativePath(file) method

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

JSON generator

Slide 39

Slide 39 text

21 A customizable JSON serializer class Person {
 String name
 String title
 int age
 String password
 Date dob
 URL favoriteUrl
 } Person person = new Person(name: 'John', title: null, 
 age: 21, password: 'secret',
 dob: Date.parse('yyyy-MM-dd', '1984-12-15'),
 favoriteUrl: new URL('http://groovy-lang.org/'))
 
 def generator = new JsonGenerator.Options()
 .excludeNulls()
 .dateFormat('yyyy@MM')
 .excludeFieldsByName('age', 'password')
 .excludeFieldsByType(URL)
 .build()
 
 assert generator.toJson(person) == 
 '{"dob":"1984@12","name":"John"}'

Slide 40

Slide 40 text

22 A customizable JSON serializer def generator = new JsonGenerator.Options()
 .addConverter(URL) { URL u, String key ->
 if (key == 'favoriteUrl') {
 u.getHost()
 } else {
 u
 }
 }
 .build()

Slide 41

Slide 41 text

22 A customizable JSON serializer def generator = new JsonGenerator.Options()
 .addConverter(URL) { URL u, String key ->
 if (key == 'favoriteUrl') {
 u.getHost()
 } else {
 u
 }
 }
 .build() Data type to customize

Slide 42

Slide 42 text

22 A customizable JSON serializer def generator = new JsonGenerator.Options()
 .addConverter(URL) { URL u, String key ->
 if (key == 'favoriteUrl') {
 u.getHost()
 } else {
 u
 }
 }
 .build() Data type to customize Optional key

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

AST macros

Slide 45

Slide 45 text

24 AST macros: the old way @Retention(RetentionPolicy.SOURCE)
 @Target([ElementType.TYPE])
 @GroovyASTTransformationClass([ "metaprogramming.AddMethodASTTransformation"]) @interface AddMethod { }

Slide 46

Slide 46 text

24 AST macros: the old way @Retention(RetentionPolicy.SOURCE)
 @Target([ElementType.TYPE])
 @GroovyASTTransformationClass([ "metaprogramming.AddMethodASTTransformation"]) @interface AddMethod { } Let’s decorate a class 
 with an additional method

Slide 47

Slide 47 text

25 AST macros: the old way @GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
 class AddMethodASTTransformation extends AbstractASTTransformation {
 void visit(ASTNode[] nodes, SourceUnit source) {
 ClassNode classNode = (ClassNode) nodes[1]
 
 ReturnStatement code =
 new ReturnStatement(
 new ConstantExpression("42"))
 
 MethodNode methodNode =
 new MethodNode("getMessage",
 ACC_PUBLIC, ClassHelper.make(String),
 [] as Parameter[], [] as ClassNode[],
 code)
 
 classNode.addMethod(methodNode)
 }
 }

Slide 48

Slide 48 text

25 AST macros: the old way @GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
 class AddMethodASTTransformation extends AbstractASTTransformation {
 void visit(ASTNode[] nodes, SourceUnit source) {
 ClassNode classNode = (ClassNode) nodes[1]
 
 ReturnStatement code =
 new ReturnStatement(
 new ConstantExpression("42"))
 
 MethodNode methodNode =
 new MethodNode("getMessage",
 ACC_PUBLIC, ClassHelper.make(String),
 [] as Parameter[], [] as ClassNode[],
 code)
 
 classNode.addMethod(methodNode)
 }
 } return 42

Slide 49

Slide 49 text

26 AST macros: the new way @GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
 class AddMethodASTTransformation extends AbstractASTTransformation {
 void visit(ASTNode[] nodes, SourceUnit source) {
 ClassNode classNode = (ClassNode) nodes[1]
 
 ReturnStatement simplestCode = macro { return "42" } 
 
 
 MethodNode methodNode =
 new MethodNode("getMessage",
 ACC_PUBLIC, ClassHelper.make(String),
 [] as Parameter[], [] as ClassNode[],
 code)
 
 classNode.addMethod(methodNode)
 }
 }

Slide 50

Slide 50 text

26 AST macros: the new way @GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
 class AddMethodASTTransformation extends AbstractASTTransformation {
 void visit(ASTNode[] nodes, SourceUnit source) {
 ClassNode classNode = (ClassNode) nodes[1]
 
 ReturnStatement simplestCode = macro { return "42" } 
 
 
 MethodNode methodNode =
 new MethodNode("getMessage",
 ACC_PUBLIC, ClassHelper.make(String),
 [] as Parameter[], [] as ClassNode[],
 code)
 
 classNode.addMethod(methodNode)
 }
 } macro method

Slide 51

Slide 51 text

26 AST macros: the new way @GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
 class AddMethodASTTransformation extends AbstractASTTransformation {
 void visit(ASTNode[] nodes, SourceUnit source) {
 ClassNode classNode = (ClassNode) nodes[1]
 
 ReturnStatement simplestCode = macro { return "42" } 
 
 
 MethodNode methodNode =
 new MethodNode("getMessage",
 ACC_PUBLIC, ClassHelper.make(String),
 [] as Parameter[], [] as ClassNode[],
 code)
 
 classNode.addMethod(methodNode)
 }
 } macro method Different variants: macro(Closure), macro(boolean, Closure) macro(CompilePhase, Closure)…

Slide 52

Slide 52 text

27 AST macros: variable substitution @Retention(RetentionPolicy.SOURCE)
 @Target([ElementType.FIELD])
 @GroovyASTTransformationClass([ "metaprogramming.MD5ASTTransformation"])
 @interface MD5 { }

Slide 53

Slide 53 text

27 AST macros: variable substitution @Retention(RetentionPolicy.SOURCE)
 @Target([ElementType.FIELD])
 @GroovyASTTransformationClass([ "metaprogramming.MD5ASTTransformation"])
 @interface MD5 { } Adds a get${stringField}MD5() method for each @MD5 String field

Slide 54

Slide 54 text

28 AST macros: variable substitution BlockStatement buildMD5MethodCode(FieldNode fieldNode) {
 VariableExpression fieldVar = GeneralUtils.varX(fieldNode.name)
 
 return macro(CompilePhase.SEMANTIC_ANALYSIS, true) {
 return java.security.MessageDigest
 .getInstance('MD5')
 .digest($v { fieldVar }.getBytes())
 .encodeHex()
 .toString()
 }
 }

Slide 55

Slide 55 text

28 AST macros: variable substitution BlockStatement buildMD5MethodCode(FieldNode fieldNode) {
 VariableExpression fieldVar = GeneralUtils.varX(fieldNode.name)
 
 return macro(CompilePhase.SEMANTIC_ANALYSIS, true) {
 return java.security.MessageDigest
 .getInstance('MD5')
 .digest($v { fieldVar }.getBytes())
 .encodeHex()
 .toString()
 }
 } Substitute this $v expression with 
 a variable from the context

Slide 56

Slide 56 text

29 AST macros: macro class @GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
 class StatisticsASTTransformation extends AbstractASTTransformation {
 void visit(ASTNode[] nodes, SourceUnit source) {
 ClassNode classNode = (ClassNode) nodes[1]
 ClassNode templateClass = buildTemplateClass(classNode)
 
 templateClass.methods.each { MethodNode node ->
 classNode.addMethod(node)
 }
 }
 
 ClassNode buildTemplateClass(ClassNode reference) {
 def methodCount = constX(reference.methods.size())
 def fieldCount = constX(reference.fields.size())
 
 return new MacroClass() {
 class Statistics {
 java.lang.Integer getMethodCount() {
 return $v { methodCount }
 }
 
 java.lang.Integer getFieldCount() {
 return $v { fieldCount }
 }


Slide 57

Slide 57 text

29 AST macros: macro class @GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
 class StatisticsASTTransformation extends AbstractASTTransformation {
 void visit(ASTNode[] nodes, SourceUnit source) {
 ClassNode classNode = (ClassNode) nodes[1]
 ClassNode templateClass = buildTemplateClass(classNode)
 
 templateClass.methods.each { MethodNode node ->
 classNode.addMethod(node)
 }
 }
 
 ClassNode buildTemplateClass(ClassNode reference) {
 def methodCount = constX(reference.methods.size())
 def fieldCount = constX(reference.fields.size())
 
 return new MacroClass() {
 class Statistics {
 java.lang.Integer getMethodCount() {
 return $v { methodCount }
 }
 
 java.lang.Integer getFieldCount() {
 return $v { fieldCount }
 }
 MacroClass not restricted to expressions & statements: you can generate methods & classes

Slide 58

Slide 58 text

30 Create custom macros withLock(lock) { // … }

Slide 59

Slide 59 text

30 Create custom macros withLock(lock) { // … } try { lock.lock() // … } finally { lock.unlock() }

Slide 60

Slide 60 text

30 Create custom macros withLock(lock) { // … } try { lock.lock() // … } finally { lock.unlock() } @Macro static Expression withLock( MacroContext ctx, Expression lockVar, ClosureExpression body) { callX(closureX( macro { try { $v { lockVar }.lock() $v { body }.call() } finally { $v { lockVar }.unlock() } } ), 'call') }

Slide 61

Slide 61 text

31 AST matchers @Retention(RetentionPolicy.SOURCE)
 @Target([ElementType.METHOD])
 @GroovyASTTransformationClass(["JokingASTTransformation"])
 @interface Joking { }

Slide 62

Slide 62 text

31 AST matchers @Retention(RetentionPolicy.SOURCE)
 @Target([ElementType.METHOD])
 @GroovyASTTransformationClass(["JokingASTTransformation"])
 @interface Joking { } @GroovyASTTransformation( phase = CompilePhase.INSTRUCTION_SELECTION)
 class JokingASTTransformation extends AbstractASTTransformation { 
 void visit(ASTNode[] nodes, SourceUnit source) {
 MethodNode methodNode = nodes[1]
 
 methodNode
 .getCode()
 .visit(
 new ConvertOnePlusOneToThree(source))
 }
 }

Slide 63

Slide 63 text

31 AST matchers @Retention(RetentionPolicy.SOURCE)
 @Target([ElementType.METHOD])
 @GroovyASTTransformationClass(["JokingASTTransformation"])
 @interface Joking { } @GroovyASTTransformation( phase = CompilePhase.INSTRUCTION_SELECTION)
 class JokingASTTransformation extends AbstractASTTransformation { 
 void visit(ASTNode[] nodes, SourceUnit source) {
 MethodNode methodNode = nodes[1]
 
 methodNode
 .getCode()
 .visit(
 new ConvertOnePlusOneToThree(source))
 }
 } class ConvertOnePlusOneToThree extends ClassCodeExpressionTransformer {
 SourceUnit sourceUnit
 
 ConvertOnePlusOneToThree(SourceUnit sourceUnit) {
 this.sourceUnit = sourceUnit
 }
 
 Expression transform(Expression exp) {
 Expression ref = macro { 1 + 1 }
 
 if (ASTMatcher.matches(ref, exp)) {
 return macro { 3 }
 }
 
 return super.transform(exp)
 }
 }

Slide 64

Slide 64 text

32 AST matchers void testTestingSumExpression() {
 use(ASTMatcher) {
 TwiceASTTransformation sample = new TwiceASTTransformation() 
 Expression referenceNode = macro {
 a + a
 }.withConstraints {
 placeholder 'a'
 }
 
 assert sample
 .sumExpression
 .matches(referenceNode)
 }
 }

Slide 65

Slide 65 text

32 AST matchers void testTestingSumExpression() {
 use(ASTMatcher) {
 TwiceASTTransformation sample = new TwiceASTTransformation() 
 Expression referenceNode = macro {
 a + a
 }.withConstraints {
 placeholder 'a'
 }
 
 assert sample
 .sumExpression
 .matches(referenceNode)
 }
 } Placeholders to match anything

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

Groovy “Parrot”!

Slide 68

Slide 68 text

34 Good ol’ do / while loops int i = 0
 
 do {
 println i++
 } while (i < 10)
 
 assert i == 10

Slide 69

Slide 69 text

35 Classical array initializers def d = new double[] {} def i = new int[] { 1, 2 }

Slide 70

Slide 70 text

36 Identity comparison is back! class Person { String name }
 
 def p1 = new Person(name: 'Marion')
 def p2 = new Person(name: 'Érine')
 def p2copy = p2 
 
 assert p2 === p2copy
 assert p1 !== p2

Slide 71

Slide 71 text

37 !in and !instanceof if (!(1324 instanceof String)) {}
 if (1234 !instanceof String) {}
 
 assert !(3 in [])
 assert 3 !in []

Slide 72

Slide 72 text

38 Safe indexing ?[ ] List list = null
 assert list?[2] == null Map map = null
 assert map?['abc'] == null

Slide 73

Slide 73 text

39 Try with resources import java.util.zip.GZIPOutputStream
 
 def input = new File('./NOTICE')
 def output = new File('/tmp/zipped.zip')
 
 // Java-style
 try (
 FileInputStream fin = new FileInputStream(input);
 GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(output))
 ) {
 byte[] buffer = new byte[4096]
 int nread = 0
 while ((nread = fin.read(buffer)) != -1) {
 out.write(buffer, 0, nread)
 }
 }

Slide 74

Slide 74 text

40 Try with resources import java.util.zip.GZIPOutputStream
 
 def input = new File('./NOTICE')
 def output = new File('/tmp/zipped.zip')
 
 // Groovy-style :-)
 try (
 fin = new FileInputStream(input)
 out = new GZIPOutputStream(new FileOutputStream(output))
 ) { out << fin }

Slide 75

Slide 75 text

41 Lambdas! import static java.util.stream.Collectors.toList
 
 (1..10).forEach((it) -> { println it })
 
 assert (1..10).stream()
 .filter((it) -> it % 2 == 0)
 .map((it) -> it * 2)
 .collect(toList()) == [4, 8, 12, 16, 20]

Slide 76

Slide 76 text

42 Lambdas! // all the shapes (x, y) -> x + y
 (x, y) -> { x + y }
 (int x, int y) -> x + y

Slide 77

Slide 77 text

42 Lambdas! // all the shapes (x, y) -> x + y
 (x, y) -> { x + y }
 (int x, int y) -> x + y implicit return

Slide 78

Slide 78 text

42 Lambdas! // all the shapes (x, y) -> x + y
 (x, y) -> { x + y }
 (int x, int y) -> x + y def c = (int x, int y = 0) -> x + y
 assert c(1) == 1 implicit return

Slide 79

Slide 79 text

42 Lambdas! // all the shapes (x, y) -> x + y
 (x, y) -> { x + y }
 (int x, int y) -> x + y def c = (int x, int y = 0) -> x + y
 assert c(1) == 1 implicit return default argument

Slide 80

Slide 80 text

43 Method references — class refs import static java.util.stream.Collectors.toList
 
 // class::staticMethod
 assert ['1', '2', '3'] ==
 [1, 2, 3].stream()
 .map(String::valueOf)
 .collect(toList())
 
 // class::instanceMethod
 assert ['A', 'B', 'C'] ==
 ['a', 'b', 'c'].stream()
 .map(String::toUpperCase)
 .collect(toList())

Slide 81

Slide 81 text

44 Method references — instance refs class Name {
 String name
 char firstLetter() { name[0] }
 static cloneOf(Name n) { new Name(name: n.name + '2') }
 }

Slide 82

Slide 82 text

44 Method references — instance refs class Name {
 String name
 char firstLetter() { name[0] }
 static cloneOf(Name n) { new Name(name: n.name + '2') }
 } // instance::instanceMethod
 def fl = new Name(name: 'Guillaume')::firstLetter
 assert fl() == 'G'
 
 // instance::staticMethod
 def n = new Name(name: 'Marion')
 def clone = n::cloneOf
 assert clone(n).name == 'Marion2'

Slide 83

Slide 83 text

45 Method references — constructor refs def r = Random::new
 println r().nextInt(10)

Slide 84

Slide 84 text

45 Method references — constructor refs def r = Random::new
 println r().nextInt(10) def arr2d = String[][]::new
 arr2d(2, 2) == [[null, null], [null, null]]

Slide 85

Slide 85 text

46 Method references — constructor refs import groovy.transform.Canonical
 
 @Canonical
 class Animal {
 String kind
 }

Slide 86

Slide 86 text

46 Method references — constructor refs import groovy.transform.Canonical
 
 @Canonical
 class Animal {
 String kind
 } def a = Animal::new
 assert a('lion').kind == 'lion'
 
 def c = Animal
 assert c::new('cat').kind == 'cat'

Slide 87

Slide 87 text

47 Elvis assignment import groovy.transform.ToString
 
 @ToString
 class Person {
 String name
 int age
 } def p = new Person(name: 'Érine')
 
 p.with {
 name = name ?: 'unknown'
 age = age ?: 4
 }
 
 assert p.toString() == 'Person(Érine, 4)'

Slide 88

Slide 88 text

47 Elvis assignment import groovy.transform.ToString
 
 @ToString
 class Person {
 String name
 int age
 } def p = new Person(name: 'Érine')
 
 p.with {
 name = name ?: 'unknown'
 age = age ?: 4
 }
 
 assert p.toString() == 'Person(Érine, 4)' p.with {
 name ?= 'unknown'
 age ?= 4
 }

Slide 89

Slide 89 text

48 JavaDoc comments saved import org.codehaus.groovy.control.*
 import static org.apache.groovy.parser.antlr4.AstBuilder.DOC_COMMENT
 
 def code = '''
 /**
 * class Foo
 * @author glaforge
 */
 class Foo {
 /** a method */
 void m(int i) {}
 }
 '''

Slide 90

Slide 90 text

48 JavaDoc comments saved import org.codehaus.groovy.control.*
 import static org.apache.groovy.parser.antlr4.AstBuilder.DOC_COMMENT
 
 def code = '''
 /**
 * class Foo
 * @author glaforge
 */
 class Foo {
 /** a method */
 void m(int i) {}
 }
 ''' def ast = new CompilationUnit().tap {
 addSource 'myscript.groovy', code
 compile Phases.SEMANTIC_ANALYSIS
 }.ast
 
 def cls = ast.classes[0] 
 assert cls.nodeMetaData[DOC_COMMENT] .contains('class Foo')
 assert cls.methods[0].nodeMetaData[DOC_COMMENT] .contains('a method')

Slide 91

Slide 91 text

49 @Groovydoc to get javadocs at runtime /** * @Groovydoc * Text content available at runtime
 */
 class A {}

Slide 92

Slide 92 text

49 @Groovydoc to get javadocs at runtime /** * @Groovydoc * Text content available at runtime
 */
 class A {} assert A.class.getAnnotation(Groovydoc).value() .contains('content available at runtime')

Slide 93

Slide 93 text

To conclude…

Slide 94

Slide 94 text

No content

Slide 95

Slide 95 text

It’s been a long road! And it’s not over yet!

Slide 96

Slide 96 text

No content

Slide 97

Slide 97 text

Thanks for your attention!

Slide 98

Slide 98 text

Questions & Answers