SlideShare une entreprise Scribd logo
1  sur  81
Télécharger pour lire hors ligne
AST – Groovy Transformers:
More than meets the eye!
IVÁN LÓPEZ
@ilopmar
Hello!
I am Iván López
@ilopmar
@madridgug http://greachconf.com
“
The best code is not code at all
1.
A little bit of theory
AST and compilation
▷ Abstract Syntax Tree
▷ AST modified during compilation
▷ Hook into the compiler phases
AST Transformations
▷ Global ▷ Local
2.
Out-of-the-box ASTs
AST transformations categories
▷ Code generation
▷ Class design
▷ Logging improvements
▷ Declarative concurrency
▷ Cloning and externalizing
▷ Safe scripting
▷ Compiler directives
▷ Dependencies handling
Code generation
@ToString
▷ Human readable toString
▷ Effective Java by Joshua Bloch (item 10)
class User {
String name
Integer age
}
def u = new User(name: 'Iván', age: 35)
println u // User@1d2a54b2
@groovy.transform.ToString
class User {
String name
Integer age
}
def u = new User(name: 'Iván', age: 35)
assert u.toString() == 'User(Iván, 35)'
class User {
String name
Integer age
}
def u = new User(name: 'Iván', age: 35)
println u // User@1d2a54b2
@groovy.transform.ToString
class User {
String name
Integer age
}
def u = new User(name: 'Iván', age: 35)
assert u.toString() == 'User(Iván, 35)'
String toString() {
def _result = new StringBuilder()
_result.append('User(')
_result.append(this.name)
_result.append(', ')
_result.append(this.age)
_result.append(')')
return _result.toString()
}
class User {
String name
Integer age
}
def u = new User(name: 'Iván', age: 35)
println u // User@1d2a54b2
@ToString
▷ includeNames, excludes, includes, includeSuper,
includeSuperProperties, includeFields, ignoreNulls,
includePackage, cache
@ToString
▷ includeNames, excludes, includes, includeSuper,
includeSuperProperties, includeFields, ignoreNulls,
includePackage, cache
@groovy.transform.ToString(includeNames = true, excludes = ['name'])
class User {
String name
Integer age
}
def u = new User(name: 'Iván', age: 35)
assert u.toString() == 'User(age:35)'
@EqualsAndHashCode
▷ Generate equals and hashCode implementations
▷ Effective Java items 8 & 9
@EqualsAndHashCode
▷ Generate equals and hashCode implementations
▷ Effective Java items 8 & 9
@groovy.transform.EqualsAndHashCode
class User {
String name
Integer age
}
def u1 = new User(name: 'Iván', age: 35)
def u2 = new User(name: 'Iván', age: 35)
assert u1 == u2
assert u1.hashCode() == u2.hashCode()
int hashCode() {
def _result = HashCodeHelper.initHash()
_result = HashCodeHelper.updateHash(_result, this.name)
_result = HashCodeHelper.updateHash(_result, this.age)
return _result
}
boolean canEqual(Object other) {
return other instanceof User
}
boolean equals(Object other) {
if (other == null) { return false }
if (this.is(other)) { return true }
if (!(other instanceof User)) { return false }
User otherTyped = ((other) as User)
if (!(otherTyped.canEqual(this))) { return false }
if (!(this.getName().is(otherTyped.getName()))) {
if (this.getName().is(this) && !(otherTyped.getName().is(otherTyped)) || !(this.getName().is(this)) && otherTyped.getName().is(otherTyped))
return false
} else {
if (!(this.getName().is(this) && otherTyped.getName().is(otherTyped))) {
if (!(this.getName() == otherTyped.getName())) {
return false
}
}
}
}
if (!(this.getAge().is(otherTyped.getAge()))) {
if (this.getAge().is(this) && !(otherTyped.getAge().is(otherTyped)) || !(this.getAge().is(this)) && otherTyped.getAge().is(otherTyped)) {
return false
} else {
if (!(this.getAge().is(this) && otherTyped.getAge().is(otherTyped))) {
if (!(this.getAge() == otherTyped.getAge())) {
return false
}
}
}
}
return true
}
@EqualsAndHashCode
▷ excludes, includes, callSuper, includeFields, cache,
useCanEqual
@EqualsAndHashCode
▷ excludes, includes, callSuper, includeFields, cache,
useCanEqual
@groovy.transform.EqualsAndHashCode(includes = 'name')
class User {
String name
Integer age
}
def u1 = new User(name: 'Iván', age: 35)
def u2 = new User(name: 'Iván', age: 42)
assert u1 == u2
assert u1.hashCode() == u2.hashCode()
@TupleConstructor
▷ Generate constructors
@TupleConstructor
▷ Generate constructors
@groovy.transform.TupleConstructor
class User {
String name
Integer age
}
@TupleConstructor
▷ Generate constructors
@groovy.transform.TupleConstructor
class User {
String name
Integer age
}
// Default map constructor
def u1 = new User(name: 'Iván', age: 35)
@TupleConstructor
▷ Generate constructors
@groovy.transform.TupleConstructor
class User {
String name
Integer age
}
// Default map constructor
def u1 = new User(name: 'Iván', age: 35)
// Generated tuple constructor
def u2 = new User('Iván', 35)
def u3 = new User('Iván')
@TupleConstructor
▷ Generate constructors
@groovy.transform.TupleConstructor
class User {
String name
Integer age
}
// Default map constructor
def u1 = new User(name: 'Iván', age: 35)
// Generated tuple constructor
def u2 = new User('Iván', 35)
def u3 = new User('Iván')
User(String name = null, Integer age = null) {
this.name = name
this.age = age
}
@TupleConstructor
▷ excludes, includes, includeFields, includeProperties,
includeSuperFields, includeSuperProperties,
callSuper, force
@Canonical
▷ @ToString + @EqualsAndHashCode +
@TupleConstructor
@Canonical
▷ @ToString + @EqualsAndHashCode +
@TupleConstructor
@groovy.transform.Canonical
class User {
String name
Integer age
}
@Canonical
▷ @ToString + @EqualsAndHashCode +
@TupleConstructor
def u1 = new User(name: 'Iván', age: 35)
assert u1.toString() == 'User(Iván, 35)' // @ToString
@groovy.transform.Canonical
class User {
String name
Integer age
}
@Canonical
▷ @ToString + @EqualsAndHashCode +
@TupleConstructor
def u2 = new User('Iván', 35) // @TupleConstructor
assert u2.toString() == 'User(Iván, 35)'
def u1 = new User(name: 'Iván', age: 35)
assert u1.toString() == 'User(Iván, 35)' // @ToString
@groovy.transform.Canonical
class User {
String name
Integer age
}
@Canonical
▷ @ToString + @EqualsAndHashCode +
@TupleConstructor
assert u1 == u2 // @EqualsAndHashCode
assert u1.hashCode() == u2.hashCode() // @EqualsAndHashCode
def u2 = new User('Iván', 35) // @TupleConstructor
assert u2.toString() == 'User(Iván, 35)'
def u1 = new User(name: 'Iván', age: 35)
assert u1.toString() == 'User(Iván, 35)' // @ToString
@groovy.transform.Canonical
class User {
String name
Integer age
}
@InheritConstructors
▷ Reduce boilerplate code when parent classes
have multiple constructors
▷ Useful when overriding exception classes
@groovy.transform.InheritConstructors
class MyException extends Exception {
}
protected MyException(String param0, Throwable param1, boolean param2, boolean param3) {
super(param0, param1, param2, param3)
}
public MyException(Throwable param0) { super(param0) }
public MyException(String param0, Throwable param1) { super(param0, param1) }
public MyException(String param0) { super(param0) }
public MyException() { super() }
@groovy.transform.InheritConstructors
class MyException extends Exception {
}
@Lazy
▷ Lazy initialization of fields
▷ Useful when creating expensive resources
▷ Effective Java item 71
class SomeBean {
@Lazy
LinkedList myField
}
class SomeBean {
@Lazy
LinkedList myField
}
public LinkedList getMyField() {
if ($myField != null) {
$myField
} else {
$myField = new LinkedList()
}
}
@Sortable
▷ Comparable interface
▷ compareTo method natural order
▷ N methods returning comparators
▷ Effective Java item 12
@groovy.transform.Sortable
class User {
String name
Integer age
Integer born
}
public int compareTo(User other) {
if (this.is(other)) return 0
Integer value = 0
value = this.name <=> other.name
if (value != 0) return value
value = this.age <=> other.age
if (value != 0) return value
value = this.born <=> other.born
if (value != 0) return value
return 0
}
private static class User$NameComparator extends AbstractComparator<User> {
public int compare(User arg0, User arg1) {
if (arg0 == arg1) return 0
if (arg0 != null && arg1 == null) return -1
if (arg0 == null && arg1 != null) return 1
return arg0.name <=> arg1.name
}
}
private static class User$AgeComparator extends AbstractComparator<User> {
...
}
@groovy.transform.Sortable
class User {
String name
Integer age
Integer born
}
@groovy.transform.Sortable
class User {
String name
Integer age
Integer born
}
def users = [
new User(name: 'Mary', age: 15, born: 2000),
new User(name: 'Peter', age: 44, born: 1970),
new User(name: 'John', age: 35, born: 1979),
]
@groovy.transform.Sortable
class User {
String name
Integer age
Integer born
}
assert users.sort(false, User.comparatorByName())*.name == ['John', 'Mary', 'Peter']
assert users.sort(false, User.comparatorByAge())*.born == [2000, 1979, 1970]
def users = [
new User(name: 'Mary', age: 15, born: 2000),
new User(name: 'Peter', age: 44, born: 1970),
new User(name: 'John', age: 35, born: 1979),
]
@groovy.transform.Sortable
class User {
String name
Integer age
Integer born
}
@Sortable
▷ includes, excludes
@groovy.transform.Sortable(excludes = 'age')
class User {
String name
Integer age
Integer born
}
def users = [
new User(name: 'Mary', age: 15, born: 2000),
new User(name: 'Peter', age: 44, born: 1970),
new User(name: 'John', age: 35, born: 1979),
]
assert users.sort(false, User.comparatorByName())*.name == ['John', 'Mary', 'Peter']
assert users.sort(false, User.comparatorByAge())*.born == [2000, 1979, 1970]
@Builder
▷ Create fluent API calls
▷ Multiple building strategies
▷ Multiple configuration options: builder name,
prefix, excludes, includes,...
▷ Effective Java item 2
@groovy.transform.builder.Builder
class User {
String name
Integer age
Integer born
}
@groovy.transform.builder.Builder
class User {
String name
Integer age
Integer born
}
def u = User.builder()
.name('Iván')
.age(35)
.born(1979)
.build()
assert u.name == 'Iván'
assert u.age == 35
assert u.born == 1979
public static class User$UserBuilder extends Object {
private String name
private Integer age
private Integer born
public User$UserBuilder() {
}
public User$UserBuilder name(String name) {
this.name = name
return this
}
public User$UserBuilder age(Integer age) {
this.age = age
return this
}
public User$UserBuilder born(Integer born) {
this.born = born
return this
}
public User build() {
User _theUser = new User()
_theUser.name = name
_theUser.age = age
_theUser.born = born
return _theUser
}
}
@groovy.transform.builder.Builder
class User {
String name
Integer age
Integer born
}
def u = User.builder()
.name('Iván')
.age(35)
.born(1979)
.build()
assert u.name == 'Iván'
assert u.age == 35
assert u.born == 1979
Class design
@Delegate
▷ Implements delegation design pattern
▷ Delegate calls on object to method on delegated
properties
▷ All public methods are delegated
import java.time.LocalDate
class Conference {
@groovy.lang.Delegate
LocalDate when
String name
}
import java.time.LocalDate
class Conference {
@groovy.lang.Delegate
LocalDate when
String name
}
def greach = new Conference(name: 'Greach', when: LocalDate.of(2015, 04, 10))
def gr8conf = new Conference(name: 'GR8Conf' when: LocalDate.of(2015, 06, 02))
def greach = new Conference(name: 'Greach', when: LocalDate.of(2015, 04, 10))
def gr8conf = new Conference(name: 'GR8Conf' when: LocalDate.of(2015, 06, 02))
assert greach.isBefore(gr8conf)
import java.time.LocalDate
class Conference {
@groovy.lang.Delegate
LocalDate when
String name
}
class Conference {
...
public boolean isAfter(ChronoLocalDate param0) {
when.isAfter(param0)
}
public boolean isBefore(ChronoLocalDate param0) {
when.isBefore(param0)
}
...
}
def greach = new Conference(name: 'Greach', when: LocalDate.of(2015, 04, 10))
def gr8conf = new Conference(name: 'GR8Conf' when: LocalDate.of(2015, 06, 02))
assert greach.isBefore(gr8conf)
import java.time.LocalDate
class Conference {
@groovy.lang.Delegate
LocalDate when
String name
}
@Immutable
▷ Create immutable classes
▷ Effective Java item 15
▷ Rules for immutability
@groovy.transform.Immutable
class User {
String name
Integer age
}
def u = new User(name: 'Iván', age: 35)
// This does not compile
// You are not allowed to overwrite
// the final class 'User'.
class Admin extends User {
}
@groovy.transform.Immutable
class User {
String name
Integer age
}
def u = new User(name: 'Iván', age: 35)
@groovy.transform.Immutable
class User {
String name
Integer age
}
def u = new User(name: 'Iván', age: 35)
try {
u.name = 'John'
} catch (ReadOnlyPropertyException e) {
println e
}
// This does not compile
// You are not allowed to overwrite
// the final class 'User'.
class Admin extends User {
}
@Memoized
▷ Cache the result of a method
@Memoized
▷ Cache the result of a method
@groovy.transform.Memoized
Long fibonacci(Integer n) {
if (n < 2) return 1
else return fibonacci(n-1) + fibonacci(n-2)
}
fibonacci(300)
@Memoized
▷ Cache the result of a method
@groovy.transform.Memoized
Long fibonacci(Integer n) {
if (n < 2) return 1
else return fibonacci(n-1) + fibonacci(n-2)
}
fibonacci(300)
@groovy.transform.Memoized
User getUserInfo(Long userId) {
// Expensive repetitive
// network operation
}
Logging improvements
@Log, @Log4j, @Log4j2, @Slf4j
▷ Static final field for the logger
@Log, @Log4j, @Log4j2, @Slf4j
@groovy.util.logging.Log4j
class MyClass {
void method() {
log.debug "My debug message"
}
}
▷ Static final field for the logger
Declarative concurrency
Declarative concurrency
▷ @Synchronized
▷ @WithReadLock
▷ @WithWriteLock
Cloning and externalizing
Cloning and externalizing
▷ @AutoClone
▷ @AutoExternalize
Safe scripting
Safe scripting
▷ @ThreadInterrupt
▷ @TimedInterrupt
▷ @ConditionalInterrupt
Compiler directives
Compiler directives
▷ @TypeChecked
▷ @CompileStatic
▷ @CompileDynamic
Dependencies handling
@Grab
▷ Grape dependency manager
@Grab
▷ Grape dependency manager
@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate
// or
@Grab('org.springframework:spring-orm:3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate
@GrabResolver
▷ Grape dependency manager
@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate
// or
@Grab('org.springframework:spring-orm:3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate
@GrabResolver(name='restlet', root='http://maven.restlet.org/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
@GrabExclude
▷ Grape dependency manager
@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate
// or
@Grab('org.springframework:spring-orm:3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate
@GrabResolver(name='restlet', root='http://maven.restlet.org/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
@Grab('net.sourceforge.htmlunit:htmlunit:2.8')
@GrabExclude('xml-apis:xml-apis')
3.
Summary
“
The best code is not code at all
Thanks!
Any questions?
@ilopmar
lopez.ivan@gmail.com
https://github.com/lmivan
Iván López
http://kcy.me/1zr7q

Contenu connexe

Tendances

Polyglot Programming in the JVM
Polyglot Programming in the JVMPolyglot Programming in the JVM
Polyglot Programming in the JVM
Andres Almiray
 
Poor Man's Functional Programming
Poor Man's Functional ProgrammingPoor Man's Functional Programming
Poor Man's Functional Programming
Dmitry Buzdin
 
Kotlin, 어떻게 동작하나요
Kotlin, 어떻게 동작하나요Kotlin, 어떻게 동작하나요
Kotlin, 어떻게 동작하나요
Chang W. Doh
 

Tendances (20)

Polyglot JVM
Polyglot JVMPolyglot JVM
Polyglot JVM
 
Designing with Groovy Traits - Gr8Conf India
Designing with Groovy Traits - Gr8Conf IndiaDesigning with Groovy Traits - Gr8Conf India
Designing with Groovy Traits - Gr8Conf India
 
Groovy presentation
Groovy presentationGroovy presentation
Groovy presentation
 
Values
ValuesValues
Values
 
Intro to Kotlin
Intro to KotlinIntro to Kotlin
Intro to Kotlin
 
The Ring programming language version 1.9 book - Part 39 of 210
The Ring programming language version 1.9 book - Part 39 of 210The Ring programming language version 1.9 book - Part 39 of 210
The Ring programming language version 1.9 book - Part 39 of 210
 
The Ring programming language version 1.5.2 book - Part 6 of 181
The Ring programming language version 1.5.2 book - Part 6 of 181The Ring programming language version 1.5.2 book - Part 6 of 181
The Ring programming language version 1.5.2 book - Part 6 of 181
 
Rxjava2 custom operator
Rxjava2 custom operatorRxjava2 custom operator
Rxjava2 custom operator
 
Polyglot Programming in the JVM
Polyglot Programming in the JVMPolyglot Programming in the JVM
Polyglot Programming in the JVM
 
Poor Man's Functional Programming
Poor Man's Functional ProgrammingPoor Man's Functional Programming
Poor Man's Functional Programming
 
The Sincerest Form of Flattery
The Sincerest Form of FlatteryThe Sincerest Form of Flattery
The Sincerest Form of Flattery
 
Guava et Lombok au Lyon JUG
Guava et Lombok au Lyon JUGGuava et Lombok au Lyon JUG
Guava et Lombok au Lyon JUG
 
Kotlin
KotlinKotlin
Kotlin
 
Kotlin, 어떻게 동작하나요
Kotlin, 어떻게 동작하나요Kotlin, 어떻게 동작하나요
Kotlin, 어떻게 동작하나요
 
Programming in python Unit-1 Part-1
Programming in python Unit-1 Part-1Programming in python Unit-1 Part-1
Programming in python Unit-1 Part-1
 
Functional Programming In Java
Functional Programming In JavaFunctional Programming In Java
Functional Programming In Java
 
The Ring programming language version 1.7 book - Part 35 of 196
The Ring programming language version 1.7 book - Part 35 of 196The Ring programming language version 1.7 book - Part 35 of 196
The Ring programming language version 1.7 book - Part 35 of 196
 
Guava et Lombok au Brezth JUG
Guava et Lombok au Brezth JUGGuava et Lombok au Brezth JUG
Guava et Lombok au Brezth JUG
 
Guava et Lombok au Lorraine JUG
Guava et Lombok au Lorraine JUGGuava et Lombok au Lorraine JUG
Guava et Lombok au Lorraine JUG
 
Pragmatic Real-World Scala (short version)
Pragmatic Real-World Scala (short version)Pragmatic Real-World Scala (short version)
Pragmatic Real-World Scala (short version)
 

Similaire à Greach 2015 AST – Groovy Transformers: More than meets the eye!

AST Transformations at JFokus
AST Transformations at JFokusAST Transformations at JFokus
AST Transformations at JFokus
HamletDRC
 
AST Transformations
AST TransformationsAST Transformations
AST Transformations
HamletDRC
 
CodeCamp Iasi 10 march 2012 - Practical Groovy
CodeCamp Iasi 10 march 2012 - Practical GroovyCodeCamp Iasi 10 march 2012 - Practical Groovy
CodeCamp Iasi 10 march 2012 - Practical Groovy
Codecamp Romania
 

Similaire à Greach 2015 AST – Groovy Transformers: More than meets the eye! (20)

AST Transformations at JFokus
AST Transformations at JFokusAST Transformations at JFokus
AST Transformations at JFokus
 
Java: Nie popełniaj tych błędów!
Java: Nie popełniaj tych błędów!Java: Nie popełniaj tych błędów!
Java: Nie popełniaj tych błędów!
 
AST Transformations
AST TransformationsAST Transformations
AST Transformations
 
CodeCamp Iasi 10 march 2012 - Practical Groovy
CodeCamp Iasi 10 march 2012 - Practical GroovyCodeCamp Iasi 10 march 2012 - Practical Groovy
CodeCamp Iasi 10 march 2012 - Practical Groovy
 
Groovy grails types, operators, objects
Groovy grails types, operators, objectsGroovy grails types, operators, objects
Groovy grails types, operators, objects
 
Meetup di GDG Italia - Leonardo Pirro - Codemotion Rome 2018
Meetup di GDG Italia - Leonardo Pirro -  Codemotion Rome 2018 Meetup di GDG Italia - Leonardo Pirro -  Codemotion Rome 2018
Meetup di GDG Italia - Leonardo Pirro - Codemotion Rome 2018
 
Pure Kotlin Devoxx PL 2021
Pure Kotlin Devoxx PL 2021Pure Kotlin Devoxx PL 2021
Pure Kotlin Devoxx PL 2021
 
Scala introduction
Scala introductionScala introduction
Scala introduction
 
Pure kotlin
Pure kotlinPure kotlin
Pure kotlin
 
Groovy Ast Transformations (greach)
Groovy Ast Transformations (greach)Groovy Ast Transformations (greach)
Groovy Ast Transformations (greach)
 
First few months with Kotlin - Introduction through android examples
First few months with Kotlin - Introduction through android examplesFirst few months with Kotlin - Introduction through android examples
First few months with Kotlin - Introduction through android examples
 
No excuses, switch to kotlin
No excuses, switch to kotlinNo excuses, switch to kotlin
No excuses, switch to kotlin
 
#살아있다 #자프링외길12년차 #코프링2개월생존기
#살아있다 #자프링외길12년차 #코프링2개월생존기#살아있다 #자프링외길12년차 #코프링2개월생존기
#살아있다 #자프링외길12년차 #코프링2개월생존기
 
Lezione03
Lezione03Lezione03
Lezione03
 
Lezione03
Lezione03Lezione03
Lezione03
 
Scala vs Java 8 in a Java 8 World
Scala vs Java 8 in a Java 8 WorldScala vs Java 8 in a Java 8 World
Scala vs Java 8 in a Java 8 World
 
Groovy closures
Groovy closuresGroovy closures
Groovy closures
 
Benefits of Kotlin
Benefits of KotlinBenefits of Kotlin
Benefits of Kotlin
 
No excuses, switch to kotlin
No excuses, switch to kotlinNo excuses, switch to kotlin
No excuses, switch to kotlin
 
OOP Lab Report.docx
OOP Lab Report.docxOOP Lab Report.docx
OOP Lab Report.docx
 

Plus de Iván López Martín

Plus de Iván López Martín (20)

SalmorejoTech 2024 - Spring Boot <3 Testcontainers
SalmorejoTech 2024 - Spring Boot <3 TestcontainersSalmorejoTech 2024 - Spring Boot <3 Testcontainers
SalmorejoTech 2024 - Spring Boot <3 Testcontainers
 
CommitConf 2024 - Spring Boot <3 Testcontainers
CommitConf 2024 - Spring Boot <3 TestcontainersCommitConf 2024 - Spring Boot <3 Testcontainers
CommitConf 2024 - Spring Boot <3 Testcontainers
 
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdfVoxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
 
VMware - Testcontainers y Spring Boot
VMware - Testcontainers y Spring BootVMware - Testcontainers y Spring Boot
VMware - Testcontainers y Spring Boot
 
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud GatewaySpring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
 
Codemotion Madrid 2023 - Testcontainers y Spring Boot
Codemotion Madrid 2023 - Testcontainers y Spring BootCodemotion Madrid 2023 - Testcontainers y Spring Boot
Codemotion Madrid 2023 - Testcontainers y Spring Boot
 
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
CommitConf 2023 - Spring Framework 6 y Spring Boot 3CommitConf 2023 - Spring Framework 6 y Spring Boot 3
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
 
Construyendo un API REST con Spring Boot y GraalVM
Construyendo un API REST con Spring Boot y GraalVMConstruyendo un API REST con Spring Boot y GraalVM
Construyendo un API REST con Spring Boot y GraalVM
 
jLove 2020 - Micronaut and graalvm: The power of AoT
jLove 2020 - Micronaut and graalvm: The power of AoTjLove 2020 - Micronaut and graalvm: The power of AoT
jLove 2020 - Micronaut and graalvm: The power of AoT
 
Codemotion Madrid 2020 - Serverless con Micronaut
Codemotion Madrid 2020 - Serverless con MicronautCodemotion Madrid 2020 - Serverless con Micronaut
Codemotion Madrid 2020 - Serverless con Micronaut
 
JConf Perú 2020 - ¡Micronaut en acción!
JConf Perú 2020 - ¡Micronaut en acción!JConf Perú 2020 - ¡Micronaut en acción!
JConf Perú 2020 - ¡Micronaut en acción!
 
JConf Perú 2020 - Micronaut + GraalVM = <3
JConf Perú 2020 - Micronaut + GraalVM = <3JConf Perú 2020 - Micronaut + GraalVM = <3
JConf Perú 2020 - Micronaut + GraalVM = <3
 
JConf México 2020 - Micronaut + GraalVM = <3
JConf México 2020 - Micronaut + GraalVM = <3JConf México 2020 - Micronaut + GraalVM = <3
JConf México 2020 - Micronaut + GraalVM = <3
 
Developing Micronaut Applications With IntelliJ IDEA
Developing Micronaut Applications With IntelliJ IDEADeveloping Micronaut Applications With IntelliJ IDEA
Developing Micronaut Applications With IntelliJ IDEA
 
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
CommitConf 2019 - Micronaut y GraalVm: La combinación perfectaCommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
 
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
 
Greach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsGreach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut Configurations
 
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
VoxxedDays Bucharest 2019 - Alexa, nice to meet youVoxxedDays Bucharest 2019 - Alexa, nice to meet you
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
 
JavaDay Lviv 2019 - Micronaut in action!
JavaDay Lviv 2019 - Micronaut in action!JavaDay Lviv 2019 - Micronaut in action!
JavaDay Lviv 2019 - Micronaut in action!
 
CrossDvlup Madrid 2019 - Alexa, encantado de conocerte
CrossDvlup Madrid 2019 - Alexa, encantado de conocerteCrossDvlup Madrid 2019 - Alexa, encantado de conocerte
CrossDvlup Madrid 2019 - Alexa, encantado de conocerte
 

Dernier

Dernier (20)

Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 

Greach 2015 AST – Groovy Transformers: More than meets the eye!

  • 1. AST – Groovy Transformers: More than meets the eye! IVÁN LÓPEZ @ilopmar
  • 2. Hello! I am Iván López @ilopmar @madridgug http://greachconf.com
  • 3. “ The best code is not code at all
  • 4. 1. A little bit of theory
  • 5. AST and compilation ▷ Abstract Syntax Tree ▷ AST modified during compilation ▷ Hook into the compiler phases
  • 8. AST transformations categories ▷ Code generation ▷ Class design ▷ Logging improvements ▷ Declarative concurrency ▷ Cloning and externalizing ▷ Safe scripting ▷ Compiler directives ▷ Dependencies handling
  • 10. @ToString ▷ Human readable toString ▷ Effective Java by Joshua Bloch (item 10)
  • 11. class User { String name Integer age } def u = new User(name: 'Iván', age: 35) println u // User@1d2a54b2
  • 12. @groovy.transform.ToString class User { String name Integer age } def u = new User(name: 'Iván', age: 35) assert u.toString() == 'User(Iván, 35)' class User { String name Integer age } def u = new User(name: 'Iván', age: 35) println u // User@1d2a54b2
  • 13. @groovy.transform.ToString class User { String name Integer age } def u = new User(name: 'Iván', age: 35) assert u.toString() == 'User(Iván, 35)' String toString() { def _result = new StringBuilder() _result.append('User(') _result.append(this.name) _result.append(', ') _result.append(this.age) _result.append(')') return _result.toString() } class User { String name Integer age } def u = new User(name: 'Iván', age: 35) println u // User@1d2a54b2
  • 14. @ToString ▷ includeNames, excludes, includes, includeSuper, includeSuperProperties, includeFields, ignoreNulls, includePackage, cache
  • 15. @ToString ▷ includeNames, excludes, includes, includeSuper, includeSuperProperties, includeFields, ignoreNulls, includePackage, cache @groovy.transform.ToString(includeNames = true, excludes = ['name']) class User { String name Integer age } def u = new User(name: 'Iván', age: 35) assert u.toString() == 'User(age:35)'
  • 16. @EqualsAndHashCode ▷ Generate equals and hashCode implementations ▷ Effective Java items 8 & 9
  • 17. @EqualsAndHashCode ▷ Generate equals and hashCode implementations ▷ Effective Java items 8 & 9 @groovy.transform.EqualsAndHashCode class User { String name Integer age } def u1 = new User(name: 'Iván', age: 35) def u2 = new User(name: 'Iván', age: 35) assert u1 == u2 assert u1.hashCode() == u2.hashCode()
  • 18. int hashCode() { def _result = HashCodeHelper.initHash() _result = HashCodeHelper.updateHash(_result, this.name) _result = HashCodeHelper.updateHash(_result, this.age) return _result } boolean canEqual(Object other) { return other instanceof User } boolean equals(Object other) { if (other == null) { return false } if (this.is(other)) { return true } if (!(other instanceof User)) { return false } User otherTyped = ((other) as User) if (!(otherTyped.canEqual(this))) { return false } if (!(this.getName().is(otherTyped.getName()))) { if (this.getName().is(this) && !(otherTyped.getName().is(otherTyped)) || !(this.getName().is(this)) && otherTyped.getName().is(otherTyped)) return false } else { if (!(this.getName().is(this) && otherTyped.getName().is(otherTyped))) { if (!(this.getName() == otherTyped.getName())) { return false } } } } if (!(this.getAge().is(otherTyped.getAge()))) { if (this.getAge().is(this) && !(otherTyped.getAge().is(otherTyped)) || !(this.getAge().is(this)) && otherTyped.getAge().is(otherTyped)) { return false } else { if (!(this.getAge().is(this) && otherTyped.getAge().is(otherTyped))) { if (!(this.getAge() == otherTyped.getAge())) { return false } } } } return true }
  • 19.
  • 20. @EqualsAndHashCode ▷ excludes, includes, callSuper, includeFields, cache, useCanEqual
  • 21. @EqualsAndHashCode ▷ excludes, includes, callSuper, includeFields, cache, useCanEqual @groovy.transform.EqualsAndHashCode(includes = 'name') class User { String name Integer age } def u1 = new User(name: 'Iván', age: 35) def u2 = new User(name: 'Iván', age: 42) assert u1 == u2 assert u1.hashCode() == u2.hashCode()
  • 24. @TupleConstructor ▷ Generate constructors @groovy.transform.TupleConstructor class User { String name Integer age } // Default map constructor def u1 = new User(name: 'Iván', age: 35)
  • 25. @TupleConstructor ▷ Generate constructors @groovy.transform.TupleConstructor class User { String name Integer age } // Default map constructor def u1 = new User(name: 'Iván', age: 35) // Generated tuple constructor def u2 = new User('Iván', 35) def u3 = new User('Iván')
  • 26. @TupleConstructor ▷ Generate constructors @groovy.transform.TupleConstructor class User { String name Integer age } // Default map constructor def u1 = new User(name: 'Iván', age: 35) // Generated tuple constructor def u2 = new User('Iván', 35) def u3 = new User('Iván') User(String name = null, Integer age = null) { this.name = name this.age = age }
  • 27. @TupleConstructor ▷ excludes, includes, includeFields, includeProperties, includeSuperFields, includeSuperProperties, callSuper, force
  • 28. @Canonical ▷ @ToString + @EqualsAndHashCode + @TupleConstructor
  • 29. @Canonical ▷ @ToString + @EqualsAndHashCode + @TupleConstructor @groovy.transform.Canonical class User { String name Integer age }
  • 30. @Canonical ▷ @ToString + @EqualsAndHashCode + @TupleConstructor def u1 = new User(name: 'Iván', age: 35) assert u1.toString() == 'User(Iván, 35)' // @ToString @groovy.transform.Canonical class User { String name Integer age }
  • 31. @Canonical ▷ @ToString + @EqualsAndHashCode + @TupleConstructor def u2 = new User('Iván', 35) // @TupleConstructor assert u2.toString() == 'User(Iván, 35)' def u1 = new User(name: 'Iván', age: 35) assert u1.toString() == 'User(Iván, 35)' // @ToString @groovy.transform.Canonical class User { String name Integer age }
  • 32. @Canonical ▷ @ToString + @EqualsAndHashCode + @TupleConstructor assert u1 == u2 // @EqualsAndHashCode assert u1.hashCode() == u2.hashCode() // @EqualsAndHashCode def u2 = new User('Iván', 35) // @TupleConstructor assert u2.toString() == 'User(Iván, 35)' def u1 = new User(name: 'Iván', age: 35) assert u1.toString() == 'User(Iván, 35)' // @ToString @groovy.transform.Canonical class User { String name Integer age }
  • 33. @InheritConstructors ▷ Reduce boilerplate code when parent classes have multiple constructors ▷ Useful when overriding exception classes
  • 35. protected MyException(String param0, Throwable param1, boolean param2, boolean param3) { super(param0, param1, param2, param3) } public MyException(Throwable param0) { super(param0) } public MyException(String param0, Throwable param1) { super(param0, param1) } public MyException(String param0) { super(param0) } public MyException() { super() } @groovy.transform.InheritConstructors class MyException extends Exception { }
  • 36. @Lazy ▷ Lazy initialization of fields ▷ Useful when creating expensive resources ▷ Effective Java item 71
  • 38. class SomeBean { @Lazy LinkedList myField } public LinkedList getMyField() { if ($myField != null) { $myField } else { $myField = new LinkedList() } }
  • 39. @Sortable ▷ Comparable interface ▷ compareTo method natural order ▷ N methods returning comparators ▷ Effective Java item 12
  • 40. @groovy.transform.Sortable class User { String name Integer age Integer born }
  • 41. public int compareTo(User other) { if (this.is(other)) return 0 Integer value = 0 value = this.name <=> other.name if (value != 0) return value value = this.age <=> other.age if (value != 0) return value value = this.born <=> other.born if (value != 0) return value return 0 } private static class User$NameComparator extends AbstractComparator<User> { public int compare(User arg0, User arg1) { if (arg0 == arg1) return 0 if (arg0 != null && arg1 == null) return -1 if (arg0 == null && arg1 != null) return 1 return arg0.name <=> arg1.name } } private static class User$AgeComparator extends AbstractComparator<User> { ... } @groovy.transform.Sortable class User { String name Integer age Integer born }
  • 42. @groovy.transform.Sortable class User { String name Integer age Integer born }
  • 43. def users = [ new User(name: 'Mary', age: 15, born: 2000), new User(name: 'Peter', age: 44, born: 1970), new User(name: 'John', age: 35, born: 1979), ] @groovy.transform.Sortable class User { String name Integer age Integer born }
  • 44. assert users.sort(false, User.comparatorByName())*.name == ['John', 'Mary', 'Peter'] assert users.sort(false, User.comparatorByAge())*.born == [2000, 1979, 1970] def users = [ new User(name: 'Mary', age: 15, born: 2000), new User(name: 'Peter', age: 44, born: 1970), new User(name: 'John', age: 35, born: 1979), ] @groovy.transform.Sortable class User { String name Integer age Integer born }
  • 45. @Sortable ▷ includes, excludes @groovy.transform.Sortable(excludes = 'age') class User { String name Integer age Integer born } def users = [ new User(name: 'Mary', age: 15, born: 2000), new User(name: 'Peter', age: 44, born: 1970), new User(name: 'John', age: 35, born: 1979), ] assert users.sort(false, User.comparatorByName())*.name == ['John', 'Mary', 'Peter'] assert users.sort(false, User.comparatorByAge())*.born == [2000, 1979, 1970]
  • 46. @Builder ▷ Create fluent API calls ▷ Multiple building strategies ▷ Multiple configuration options: builder name, prefix, excludes, includes,... ▷ Effective Java item 2
  • 47. @groovy.transform.builder.Builder class User { String name Integer age Integer born }
  • 48. @groovy.transform.builder.Builder class User { String name Integer age Integer born } def u = User.builder() .name('Iván') .age(35) .born(1979) .build() assert u.name == 'Iván' assert u.age == 35 assert u.born == 1979
  • 49. public static class User$UserBuilder extends Object { private String name private Integer age private Integer born public User$UserBuilder() { } public User$UserBuilder name(String name) { this.name = name return this } public User$UserBuilder age(Integer age) { this.age = age return this } public User$UserBuilder born(Integer born) { this.born = born return this } public User build() { User _theUser = new User() _theUser.name = name _theUser.age = age _theUser.born = born return _theUser } } @groovy.transform.builder.Builder class User { String name Integer age Integer born } def u = User.builder() .name('Iván') .age(35) .born(1979) .build() assert u.name == 'Iván' assert u.age == 35 assert u.born == 1979
  • 51. @Delegate ▷ Implements delegation design pattern ▷ Delegate calls on object to method on delegated properties ▷ All public methods are delegated
  • 52. import java.time.LocalDate class Conference { @groovy.lang.Delegate LocalDate when String name }
  • 53. import java.time.LocalDate class Conference { @groovy.lang.Delegate LocalDate when String name } def greach = new Conference(name: 'Greach', when: LocalDate.of(2015, 04, 10)) def gr8conf = new Conference(name: 'GR8Conf' when: LocalDate.of(2015, 06, 02))
  • 54. def greach = new Conference(name: 'Greach', when: LocalDate.of(2015, 04, 10)) def gr8conf = new Conference(name: 'GR8Conf' when: LocalDate.of(2015, 06, 02)) assert greach.isBefore(gr8conf) import java.time.LocalDate class Conference { @groovy.lang.Delegate LocalDate when String name }
  • 55. class Conference { ... public boolean isAfter(ChronoLocalDate param0) { when.isAfter(param0) } public boolean isBefore(ChronoLocalDate param0) { when.isBefore(param0) } ... } def greach = new Conference(name: 'Greach', when: LocalDate.of(2015, 04, 10)) def gr8conf = new Conference(name: 'GR8Conf' when: LocalDate.of(2015, 06, 02)) assert greach.isBefore(gr8conf) import java.time.LocalDate class Conference { @groovy.lang.Delegate LocalDate when String name }
  • 56. @Immutable ▷ Create immutable classes ▷ Effective Java item 15 ▷ Rules for immutability
  • 57. @groovy.transform.Immutable class User { String name Integer age } def u = new User(name: 'Iván', age: 35)
  • 58. // This does not compile // You are not allowed to overwrite // the final class 'User'. class Admin extends User { } @groovy.transform.Immutable class User { String name Integer age } def u = new User(name: 'Iván', age: 35)
  • 59. @groovy.transform.Immutable class User { String name Integer age } def u = new User(name: 'Iván', age: 35) try { u.name = 'John' } catch (ReadOnlyPropertyException e) { println e } // This does not compile // You are not allowed to overwrite // the final class 'User'. class Admin extends User { }
  • 60. @Memoized ▷ Cache the result of a method
  • 61. @Memoized ▷ Cache the result of a method @groovy.transform.Memoized Long fibonacci(Integer n) { if (n < 2) return 1 else return fibonacci(n-1) + fibonacci(n-2) } fibonacci(300)
  • 62. @Memoized ▷ Cache the result of a method @groovy.transform.Memoized Long fibonacci(Integer n) { if (n < 2) return 1 else return fibonacci(n-1) + fibonacci(n-2) } fibonacci(300) @groovy.transform.Memoized User getUserInfo(Long userId) { // Expensive repetitive // network operation }
  • 64. @Log, @Log4j, @Log4j2, @Slf4j ▷ Static final field for the logger
  • 65. @Log, @Log4j, @Log4j2, @Slf4j @groovy.util.logging.Log4j class MyClass { void method() { log.debug "My debug message" } } ▷ Static final field for the logger
  • 67. Declarative concurrency ▷ @Synchronized ▷ @WithReadLock ▷ @WithWriteLock
  • 69. Cloning and externalizing ▷ @AutoClone ▷ @AutoExternalize
  • 71. Safe scripting ▷ @ThreadInterrupt ▷ @TimedInterrupt ▷ @ConditionalInterrupt
  • 73. Compiler directives ▷ @TypeChecked ▷ @CompileStatic ▷ @CompileDynamic
  • 76. @Grab ▷ Grape dependency manager @Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate // or @Grab('org.springframework:spring-orm:3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate
  • 77. @GrabResolver ▷ Grape dependency manager @Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate // or @Grab('org.springframework:spring-orm:3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate @GrabResolver(name='restlet', root='http://maven.restlet.org/') @Grab(group='org.restlet', module='org.restlet', version='1.1.6')
  • 78. @GrabExclude ▷ Grape dependency manager @Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate // or @Grab('org.springframework:spring-orm:3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate @GrabResolver(name='restlet', root='http://maven.restlet.org/') @Grab(group='org.restlet', module='org.restlet', version='1.1.6') @Grab('net.sourceforge.htmlunit:htmlunit:2.8') @GrabExclude('xml-apis:xml-apis')
  • 80. “ The best code is not code at all