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.
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.
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'
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() }
6 Prevent @TupleConstructor default ctors @TupleConstructor(defaults = true) class Person { String first, last int age } Generates only: Person(String first, String last, int age) { /*...*/ }
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 }
@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
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 } }
14 @Delegate on getters too class Person { String name @Delegate String getName() { name.reverse() } } def p = new Person(name: 'Erine') assert p.toUpperCase() == 'ENIRE'
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 }
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
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()
18 With… tap… assert new Person().with { name = 'Guillaume' age = 39 return it }.toString() == 'Person(Guillaume, 39)' @Canonical class Person { String name int age }
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 }
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
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/'))
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
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
24 AST macros: the old way @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @GroovyASTTransformationClass([ "metaprogramming.AddMethodASTTransformation"]) @interface AddMethod { }
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
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
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
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
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')