greach 2015 ast – groovy transformers: more than meets the eye!

81
AST – Groovy Transformers: More than meets the eye! IVÁN LÓPEZ @ilopmar

Upload: ivan-lopez

Post on 15-Jul-2015

744 views

Category:

Technology


1 download

TRANSCRIPT

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.ToStringclass 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.ToStringclass 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 & [email protected] 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 == u2assert 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 == u2assert u1.hashCode() == u2.hashCode()

@TupleConstructor

▷ Generate constructors

@TupleConstructor

▷ Generate constructors

@groovy.transform.TupleConstructorclass User { String name Integer age}

@TupleConstructor

▷ Generate constructors

@groovy.transform.TupleConstructorclass User { String name Integer age}

// Default map constructordef u1 = new User(name: 'Iván', age: 35)

@TupleConstructor

▷ Generate constructors

@groovy.transform.TupleConstructorclass User { String name Integer age}

// Default map constructordef u1 = new User(name: 'Iván', age: 35)

// Generated tuple constructordef u2 = new User('Iván', 35)def u3 = new User('Iván')

@TupleConstructor

▷ Generate constructors

@groovy.transform.TupleConstructorclass User { String name Integer age}

// Default map constructordef u1 = new User(name: 'Iván', age: 35)

// Generated tuple constructordef 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.Canonicalclass 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.Canonicalclass User { String name Integer age}

@Canonical

▷ @ToString + @EqualsAndHashCode + @TupleConstructor

def u2 = new User('Iván', 35) // @TupleConstructorassert 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.Canonicalclass User { String name Integer age}

@Canonical

▷ @ToString + @EqualsAndHashCode + @TupleConstructor

assert u1 == u2 // @EqualsAndHashCodeassert u1.hashCode() == u2.hashCode() // @EqualsAndHashCode

def u2 = new User('Iván', 35) // @TupleConstructorassert 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.Canonicalclass User { String name Integer age}

@InheritConstructors

▷ Reduce boilerplate code when parent classes have multiple constructors

▷ Useful when overriding exception classes

@groovy.transform.InheritConstructorsclass 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.InheritConstructorsclass 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.Sortableclass 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.Sortableclass User { String name Integer age Integer born}

@groovy.transform.Sortableclass 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.Sortableclass 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.Sortableclass 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.Builderclass User { String name Integer age Integer born}

@groovy.transform.builder.Builderclass 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 == 35assert 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.Builderclass 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 == 35assert 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.Immutableclass 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.Immutableclass User { String name Integer age}

def u = new User(name: 'Iván', age: 35)

@groovy.transform.Immutableclass 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.MemoizedLong 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.MemoizedLong fibonacci(Integer n) { if (n < 2) return 1 else return fibonacci(n-1) + fibonacci(n-2)}

fibonacci(300)

@groovy.transform.MemoizedUser 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.Log4jclass 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

[email protected]

https://github.com/lmivan

Iván López

http://kcy.me/1zr7q