Upgrade to Pro — share decks privately, control downloads, hide ads and more …

What's new in Apache Groovy 2.4, 2.5, 3.0....

What's new in Apache Groovy 2.4, 2.5, 3.0....

At BreizhCamp 2017, Guillaume Laforge and Cédric Champeau presented about the latest developments about Apache Groovy: in the current 2.4 branch, in the new 2.5 alpha, and what's coming in 2.6 and beyond.

Guillaume Laforge

April 21, 2017
Tweet

More Decks by Guillaume Laforge

Other Decks in Technology

Transcript

  1. 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.

    View Slide

  2. 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)'

    View Slide

  3. 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

    View Slide

  4. 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.

    View Slide

  5. 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'

    View Slide

  6. 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()
    }

    View Slide

  7. 5
    Prevent @TupleConstructor default ctors
    @TupleConstructor

    class Person {

    String first, last

    int age

    }

    View Slide

  8. 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) }

    View Slide

  9. 6
    Prevent @TupleConstructor default ctors
    @TupleConstructor(defaults = true)

    class Person {
    String first, last

    int age

    }

    View Slide

  10. 6
    Prevent @TupleConstructor default ctors
    @TupleConstructor(defaults = true)

    class Person {
    String first, last

    int age

    }
    Generates only:
    Person(String first, String last, int age) { /*...*/ }

    View Slide

  11. 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)'

    View Slide

  12. 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

    View Slide

  13. 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()

    View Slide

  14. 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.

    View Slide

  15. 9
    @Immutable support in class hierarchy
    import groovy.transform.*


    @EqualsAndHashCode

    class Person {

    String name

    }

    View Slide

  16. 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

    }

    View Slide

  17. 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)'

    View Slide

  18. 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'

    View Slide

  19. 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

    View Slide

  20. 11
    New @AutoImplement transformation
    @AutoImplement

    class MyNames extends AbstractList implements Closeable {}

    View Slide

  21. 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

    }

    }

    View Slide

  22. 12
    New @AutoImplement transformation
    @AutoImplement(exception = IOException)

    class MyWriter extends Writer { }

    View Slide

  23. 12
    New @AutoImplement transformation
    @AutoImplement(exception = IOException)

    class MyWriter extends Writer { }
    @AutoImplement(exception = UnsupportedOperationException,

    message = 'Not supported by MyIterator')

    class MyIterator implements Iterator { }

    View Slide

  24. 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 }

    }

    View Slide

  25. 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

    View Slide

  26. 14
    @Delegate on getters too
    class Person {

    String name
    @Delegate

    String getName() {
    name.reverse()
    }
    }
    def p = new Person(name: 'Erine')
    assert p.toUpperCase() == 'ENIRE'

    View Slide

  27. 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
    }

    View Slide

  28. 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

    View Slide

  29. 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()

    View Slide

  30. 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'}"

    View Slide

  31. 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()

    }

    View Slide

  32. 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'

    View Slide

  33. 18
    With… tap…
    @Canonical

    class Person {

    String name

    int age

    }

    View Slide

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

    class Person {

    String name

    int age

    }

    View Slide

  35. 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

    }

    View Slide

  36. 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

    View Slide

  37. View Slide

  38. JSON
    generator

    View Slide

  39. 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('[email protected]')

    .excludeFieldsByName('age', 'password')

    .excludeFieldsByType(URL)

    .build()


    assert generator.toJson(person) == 

    '{"dob":"[email protected]","name":"John"}'

    View Slide

  40. 22
    A customizable JSON serializer
    def generator = new JsonGenerator.Options()

    .addConverter(URL) { URL u, String key ->

    if (key == 'favoriteUrl') {

    u.getHost()

    } else {

    u

    }

    }

    .build()

    View Slide

  41. 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

    View Slide

  42. 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

    View Slide

  43. View Slide

  44. AST
    macros

    View Slide

  45. 24
    AST macros: the old way
    @Retention(RetentionPolicy.SOURCE)

    @Target([ElementType.TYPE])

    @GroovyASTTransformationClass([
    "metaprogramming.AddMethodASTTransformation"])
    @interface AddMethod { }

    View Slide

  46. 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

    View Slide

  47. 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)

    }

    }

    View Slide

  48. 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

    View Slide

  49. 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)

    }

    }

    View Slide

  50. 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

    View Slide

  51. 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)…

    View Slide

  52. 27
    AST macros: variable substitution
    @Retention(RetentionPolicy.SOURCE)

    @Target([ElementType.FIELD])

    @GroovyASTTransformationClass([
    "metaprogramming.MD5ASTTransformation"])

    @interface MD5 { }

    View Slide

  53. 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

    View Slide

  54. 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()

    }

    }

    View Slide

  55. 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

    View Slide

  56. 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 }

    }


    View Slide

  57. 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

    View Slide

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

    View Slide

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

    View Slide

  60. 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')
    }

    View Slide

  61. 31
    AST matchers
    @Retention(RetentionPolicy.SOURCE)

    @Target([ElementType.METHOD])

    @GroovyASTTransformationClass(["JokingASTTransformation"])

    @interface Joking { }

    View Slide

  62. 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))

    }

    }

    View Slide

  63. 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)

    }

    }

    View Slide

  64. 32
    AST matchers
    void testTestingSumExpression() {

    use(ASTMatcher) {

    TwiceASTTransformation sample = new TwiceASTTransformation()

    Expression referenceNode = macro {

    a + a

    }.withConstraints {

    placeholder 'a'

    }


    assert sample

    .sumExpression

    .matches(referenceNode)

    }

    }

    View Slide

  65. 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

    View Slide

  66. View Slide

  67. Groovy
    “Parrot”!

    View Slide

  68. 34
    Good ol’ do / while loops
    int i = 0


    do {

    println i++

    } while (i < 10)


    assert i == 10

    View Slide

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

    View Slide

  70. 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

    View Slide

  71. 37
    !in and !instanceof
    if (!(1324 instanceof String)) {}

    if (1234 !instanceof String) {}


    assert !(3 in [])

    assert 3 !in []

    View Slide

  72. 38
    Safe indexing ?[ ]
    List list = null

    assert list?[2] == null
    Map map = null

    assert map?['abc'] == null

    View Slide

  73. 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)

    }

    }

    View Slide

  74. 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
    }

    View Slide

  75. 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]

    View Slide

  76. 42
    Lambdas!
    // all the shapes
    (x, y) -> x + y

    (x, y) -> { x + y }

    (int x, int y) -> x + y

    View Slide

  77. 42
    Lambdas!
    // all the shapes
    (x, y) -> x + y

    (x, y) -> { x + y }

    (int x, int y) -> x + y
    implicit
    return

    View Slide

  78. 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

    View Slide

  79. 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

    View Slide

  80. 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())

    View Slide

  81. 44
    Method references — instance refs
    class Name {

    String name

    char firstLetter() { name[0] }

    static cloneOf(Name n) { new Name(name: n.name + '2') }

    }

    View Slide

  82. 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'

    View Slide

  83. 45
    Method references — constructor refs
    def r = Random::new

    println r().nextInt(10)

    View Slide

  84. 45
    Method references — constructor refs
    def r = Random::new

    println r().nextInt(10)
    def arr2d = String[][]::new

    arr2d(2, 2) == [[null, null], [null, null]]

    View Slide

  85. 46
    Method references — constructor refs
    import groovy.transform.Canonical


    @Canonical

    class Animal {

    String kind

    }

    View Slide

  86. 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'

    View Slide

  87. 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)'

    View Slide

  88. 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

    }

    View Slide

  89. 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) {}

    }

    '''

    View Slide

  90. 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')

    View Slide

  91. 49
    @Groovydoc to get javadocs at runtime
    /**
    * @Groovydoc
    * Text content available at runtime

    */

    class A {}

    View Slide

  92. 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')

    View Slide

  93. To conclude…

    View Slide

  94. View Slide

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

    View Slide

  96. View Slide

  97. Thanks for your
    attention!

    View Slide

  98. Questions
    & Answers

    View Slide