SlideShare una empresa de Scribd logo
1 de 88
Groovy Code Generation Hamlet D'Arcy – Canoo Engineering AG @HamletDRC http://hamletdarcy.blogspot.com
[object Object],[object Object],[object Object]
[object Object],[object Object]
I shot an elephant in my pajamas.
Subject: I Verb: shot Direct Object: an elephant Indirect Object: in my pajamas
I shot an elephant in my pajamas. How he got in my pajamas,  I'll never know.
Subject: I Verb: shot an elephant in my pajamas Participle Phrase
We invited the strippers, JFK and Stalin
We invited the strippers, JFK and Stalin
Subject: We Verb: invited Participle: JFK strippers Stalin
We invited the strippers, JFK and Stalin
Subject: We Verb: invited Participle Phrase: the strippers JFK Stalin
[object Object]
[object Object]
[object Object]
public class   Person { private  String  name ;  public void  setName(String name) {  this . name  = name;} public  String getNameName() {  return   name ;  } public   static   void  main(String[] args) { Person p =  new  Person();   p.setName( “Hamlet” );  System. out .println(p);  } }
[object Object]
[object Object]
public class  Person { private  String  firstName ;  private  String  lastName ;  public void  setFirstName(String fName) {  this . firstName  = fName; } public  String getFirstName() {  return   firstName ;  } public void  setLastName(String lName) {  this . lastName  = lName; } public  String getLastName() {  return   lastName ;  } public  String toString() {  return  String. format ( “Person{%s,%s}” ,  firstName ,  lastName );  } public boolean  equals(Object o) { return  ReflectiveEqualsBuilder. build ( this , o); } public int  hashCode() { return  ReflectiveHashCodeBuilder. build ( this ); } } ,[object Object]
import  lombok.Data;  @Data public class  Person { private  String  firstName ;  private  String  lastName ;  } ,[object Object]
@Getter / @Setter @ToString @EqualsAndHashCode @NoArgsConstructor @RequiredArgsConstructor  @AllArgsConstructor @Data @Cleanup @Synchronized @SneakyThrows @Log @Delegate val ,[object Object]
Generates Java Boilerplate Compile Time Only –  For Eclipse and javac –  IDEA & NetBeans too Removable with delombok –  Javadoc –  GWT Read the fine print –  Know what is generated –  Slows compilation times? ,[object Object]
[object Object],AST Transformations:  Annotation Processor Javac Handler Eclipse Handler ,[object Object]
 
 
 
 
// javac private   void  generatePropertyChangeSupportField(JavacNode node) { if  (fieldAlreadyExists( PROPERTY_SUPPORT_FIELD_NAME , node))  return ; JCExpression exp = chainDots(node.getTreeMaker(), node,  "this" ); JCVariableDecl fieldDecl = newField() .ofType(PropertyChangeSupport. class ) .withName( PROPERTY_SUPPORT_FIELD_NAME ) .withModifiers( PRIVATE  |  FINAL ) .withArgs(exp) .buildWith(node); injectField(node, fieldDecl); } // ECJ private   void  generatePropertyChangeSupportField(EclipseNode node) { if  (fieldAlreadyExists( PROPERTY_SUPPORT_FIELD_NAME , node))  return ; Expression exp = referenceForThis(node.get()); FieldDeclaration fieldDecl = newField() .ofType(PropertyChangeSupport. class ) .withName( PROPERTY_SUPPORT_FIELD_NAME ) .withModifiers( PRIVATE  |  FINAL ) .withArgs(exp) .buildWith(node); injectField(node, fieldDecl); } Alex Ruiz – Custom AST  Transformations with Project Lombok http://www.ibm.com/developerworks/java/library/j-lombok/
[object Object]
class Event { String title } class Event { String title public void getTitle() { title } public String setTitle(String t) { this.title = t  } } ,[object Object]
class Event { @Delegate Date when } class Event implements Comparable, Clonable { Date when boolean after(Date when) { this.when.after(when) } boolean before(Date when)  { this.when.before(when) } Object clone() { this.when.clone() }  Int compareTo(Date anotherDate)  { this.when.compareTo(otherDate)  } Int getDate()  { this.when.date } Int getDay()  { this.when.day } Int getHours()  { this.when.hours } Int getMinutes()  { this.when.minutes } Int getMonth()  { this.when.month } Int getSeconds()  { this.when.seconds } long getTime()  { this.when.time } Int getTimezoneOffset() {  this.when.timezoneOffset }  Int getYear()  { this.when.year } void setDate(int date) { this.when.date = date }  void setHours(int hours)  { this.when.hours = hours } void setMinutes(int minutes)  { this.when.minutes = minutes } void setMonth(int month)  { this.when.month = month } void setSeconds(int seconds) { this.when.seconds = seconds }  void setTime(long time) { this.when.time = time }  void setYear(int year)  { this.when.year = year } String toGMTString()  { this.when.toGMTString() } String toLocaleString() {  this.when.toLocaleString() }  } ,[object Object]
class Event { @Lazy ArrayList speakers } class Event { ArrayList speakers  def getSpeakers() { if (speakers != null) { return speakers } else { synchronized(this) { if (speakers == null) { speakers = [] }  return speakers } } } } ,[object Object]
@Immutable  class Event { String title } ,[object Object],–  Properties must be @Immutable or effectively immutable –  Properties are private –  Mutatators throw ReadOnlyPropertyException –  Map constructor created –  Tuple constructor created –  Equals(), hashCode() & toString() created –  Dates, Clonables, & arrays are defensively copied on way in & out (but not deeply cloned) –  Collections & Maps are wrapped in Immutable variants –  Non-immutable fields force an error –  Special handling for Date, Color, etc –  Many generated methods configurable ,[object Object]
[object Object],[object Object],[object Object]
[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]
Declarative Concurrency ,[object Object],Easier Cloning and Externalizing ,[object Object],Safer Scripting ,[object Object],[object Object],[object Object],[object Object]
[object Object],Local AST Transformations ,[object Object]
 
 
 
 
class  Event {   @Delegate Date when } @GroovyASTTransformationClass( "org.pkg.DelegateTransform" ) public  @ interface  Delegate {   ... } @GroovyASTTransformation(phase =  CANONICALIZATION ) public class   DelegateTransform   implements  ASTTransformation {   public void  visit(ASTNode[] nodes, SourceUnit source) {   ...   } } ,[object Object]
[object Object],[object Object]
[object Object],import   org.codehaus.groovy.ast.* import   org.codehaus.groovy.ast.stmt.* import   org.codehaus.groovy.ast.expr.* def   ast =  new  ReturnStatement( new  ConstructorCallExpression( ClassHelper. make (Date),   ArgumentListExpression. EMPTY_ARGUMENTS   ) ) assert  ast  instanceof  ReturnStatement ,[object Object]
[object Object],def  ast =  new  AstBuilder().buildFromSpec { returnStatement { constructorCall(Date) { argumentList {} } } } assert  ast[0]  instanceof  ReturnStatement ,[object Object]
[object Object],def  ast =  new  AstBuilder().buildFromString( 'new Date()' ) assert  assert ast[0]  instanceof  BlockStatement assert  ast[0].statements[0]  instanceof  ReturnStatement ,[object Object]
[object Object],def  ast =  new  AstBuilder().buildFromCode { new  Date() } assert  ast[0].statements[0]  instanceof  ReturnStatement ,[object Object]
[object Object],class  MainExample { @Main public   void  greet() { println  "Hello from greet()!" } } $ groovy MainExample.groovy  Hello from greet()! ,[object Object]
MethodNode makeMainMethod(MethodNode source) { def  className = source.declaringClass.name def  methodName = source.name def  ast =  new  AstBuilder().buildFromString( INSTRUCTION_SELECTION ,  false ,  """ package $source.declaringClass.packageName class $source.declaringClass.nameWithoutPackage { public static void main(String[] args) { new $className().$methodName() } } """ ) ast[ 1 ].methods.find { it.name ==  'main'  } } ,[object Object]
[object Object],[object Object]
How it Works Compiles Java to AST Analyzes AST Nodes Rewrites with Strings
(x != null && x instanceof SomeClass)
(x != null && x instanceof SomeClass) @Override public void  visitBinaryExpression(PsiBinaryExpression exp) { if  (exp.token ==  '==' ) { if  (exp.left instanceof BinaryExpression ... ) { if  (exp.right instanceof BinaryExpression ... ) {   registerError(exp,  '...' ) } } } }
(x != null && x instanceof SomeClass) @Override public void visitBinaryExpression(PsiBinaryExpression expression) { super.visitBinaryExpression(expression); if (!expression.getOperationTokenType().equals(JavaTokenType.ANDAND)) { return; } PsiExpression lOperand = expression.getLOperand(); PsiExpression rOperand = expression.getROperand(); if (!isBinaryExpressionAndInstanceOfCheck(lOperand, rOperand) && !isBinaryExpressionAndInstanceOfCheck(rOperand, lOperand)) { return; } PsiBinaryExpression binaryExpression = getInnerBinaryExpression(expression); if (!binaryExpression.getOperationTokenType().equals(JavaTokenType.NE)) { return; } boolean leftIsNullCheck = isNullCheck(binaryExpression.getLOperand(), binaryExpression.getROperand()); boolean rightIsNullCheck = isNullCheck(binaryExpression.getROperand(), binaryExpression.getLOperand()); if (!leftIsNullCheck && !rightIsNullCheck) return; PsiReferenceExpression theReference = getReferenceFromNotNullCheck(binaryExpression); PsiExpression instanceOfOperand = getInstanceOfOperand(expression); if (!(instanceOfOperand instanceof PsiReferenceExpression)) { return; } String referenceNameA = (theReference).getReferenceName(); String referenceNameB = ((PsiReferenceExpression)instanceOfOperand).getReferenceName(); if (referenceNameA == null || referenceNameB == null) { return; } if (!referenceNameA.equals(referenceNameB)) { return; } registerError(expression); }
Rewrite: (x instanceof SomeClass) PsiElement andand = descriptor.getPsiElement(); if  (andand  instanceof  PsiBinaryExpression) { PsiExpression lhs = ((PsiBinaryExpression)andand).getLOperand(); PsiExpression rhs = ((PsiBinaryExpression)andand).getROperand(); if  (lhs  instanceof  PsiInstanceOfExpression) { replaceExpression((PsiBinaryExpression)andand, lhs.getText()); }  else if  (rhs  instanceof  PsiInstanceOfExpression) { replaceExpression((PsiBinaryExpression)andand, rhs.getText()); } }
Rewrite: (x instanceof SomeClass) when  (descriptor.psiElement) { is  PsiBinaryExpression @ ( val  lhs  is  PsiInstanceOfExpression, *) => replaceExpression(descriptor.psiElement, lhs.text)  is  PsiBinaryExpression @ (*,  val  rhs  is  PsiInstanceOfExpression) => replaceExpression(descriptor.psiElement, rhs.text)  } Kotlin's “Decomposer patterns”
How it Works Compiles Java to AST Analyzes AST Nodes Rewrites with Strings
[object Object],[object Object]
–  Dead Code –  Defects like GString as Map Key, Duplicate Map Key –  Return path analysis (null returns) –  Type inference (and improving) –  Concurrency Problems (busy wait, etc) –  Poor Testing Practices –  Un-Groovy code …  and 255+ more rules
How it works CodeNarc Rule:  Ban new java.util.Random() calls @Override void  visitConstructorCallExpression(   ConstructorCallExpression call) { if  (AstUtil.classNodeImplementsType(call. type , Random)) {   addViolation(call,  'Using Random is insecure...' )   }   super .visitConstructorCallExpression(call) }
How it works
How it works
How it works
[object Object],[object Object],[object Object],[object Object]
[object Object],From Terrence Parr's  “ Language Implementation Patterns” ,[object Object],[object Object]
[object Object],//FinallyStatement//ReturnStatement //SynchronizedStatement/Block[1][count(*) = 0] //AllocationExpression/ClassOrInterfaceType   [contains(@Image,'ThreadGroup')] | //PrimarySuffix   [contains(@Image, 'getThreadGroup')] ,[object Object],[object Object]
How it Works Compiles Groovy to AST Analyzes AST Nodes –  Not well typed –  Not many tokens –  unreliable with other AST Transforms
Embedded Languages def   s =  new   ArithmeticShell() assert  2 == s.evaluate( ' 1+1 ' ) assert  1.0 == s.evaluate( 'cos(2*PI)' ) public interface   GroovyCodeVisitor {   void  visitBlockStatement(BlockStatement statement)   void  visitForLoop(ForStatement forLoop)   void  visitWhileLoop(WhileStatement loop)   void  visitDoWhileLoop(DoWhileStatement loop)   ... }
[object Object],[object Object],[object Object],[object Object]
[object Object],def   "Does simple math work?" () {   expect:   def  s =  new  ArithmeticShell()   s.evaluate(input) == output   where:   input  | output   '1 + 1'   | 2   'cos(2*PI)'   | 1.0 } ,[object Object],[object Object]
[object Object],Transform.jar | - transforms   | - global   | - CompiledAtASTTransformation.class   | - CompiledAtASTTransformation$_visit_closure1.class   | - ... | META-INF   | - services   | - org.codehaus.groovy.transform.ASTTransformation   | - org.codehaus.groovy.source.Extensions ,[object Object],[object Object]
[object Object],[object Object]
Ruby FizzBuzz 1.upto(100) do |n| print "Fizz" if a = ((n % 3) == 0) print "Buzz" if b = ((n % 5) == 0)  print n unless (a || b) print "" end
Mirah FizzBuzz 1.upto(100) do |n| print "Fizz" if a = ((n % 3) == 0) print "Buzz" if b = ((n % 5) == 0)  print n unless (a || b) print "" end
Mirah: Pure JVM Class Output public class Fizz-buzz { public static void main(String[] argv) { ... do { n = __xform_tmp_4; ... if (n % 15 == 0) System.out.println(&quot;FizzBuzz&quot;); else if (n % 5 == 0) System.out.println(&quot;Buzz&quot;); else if (n % 3 == 0) System.out.println(&quot;Fizz&quot;); else System.out.println(n); ... } while (__xform_tmp_4 <= __xform_tmp_5); } }
Mirah: .java File Output if (((n % 15) == 0)) { PrintStream temp$10 = System.out; temp$10.println(&quot;FizzBuzz&quot;); } else { if (((n % 5) == 0)) { PrintStream temp$11 = System.out; temp$11.println(&quot;Buzz&quot;); } else { if (((n % 3) == 0)) { PrintStream temp$12 = System.out; temp$12.println(&quot;Fizz&quot;); } else { PrintStream temp$13 = System.out; temp$13.println(n); } } }
What it Means –  Fast –  Lightweight –  Uses JDK –  Runs on Android? –  Runs on GWT?
How it Works
How it Works
 
Mirah Macros macro def eachChar(value, &block) quote {  `value`.toCharArray.each do | my_char | `block.body` end  } end eachChar('Hola Greach Conference!') do | my_char | puts my_char end
Mirah Macros class Person  make_attr :firstName, :string  end  p = Person.new p.firstName = 'hamlet' puts p.firstName
Mirah Macros macro def make_attr(name, type)  attribute_name = name.string_value() quote { def `name`  @`name`  end  def `&quot;#{attribute_name}_set&quot;`(value:`type`)  @`name` = value end  } end
[object Object],[object Object]
Difficult to find call-sites
Still Difficult to generate AST Macros are more general ,[object Object]
Easily quote ASTs into source
Static types are a big win for transformations ,[object Object]

Más contenido relacionado

La actualidad más candente

Kotlin Developer Starter in Android projects
Kotlin Developer Starter in Android projectsKotlin Developer Starter in Android projects
Kotlin Developer Starter in Android projectsBartosz Kosarzycki
 
Lambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive CodeLambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive CodeIan Robertson
 
C# for-java-developers
C# for-java-developersC# for-java-developers
C# for-java-developersDhaval Dalal
 
Basic Javascript
Basic JavascriptBasic Javascript
Basic JavascriptBunlong Van
 
Javascript basic course
Javascript basic courseJavascript basic course
Javascript basic courseTran Khoa
 
AST Transformations: Groovy’s best kept secret by Andres Almiray
AST Transformations: Groovy’s best kept secret by Andres AlmirayAST Transformations: Groovy’s best kept secret by Andres Almiray
AST Transformations: Groovy’s best kept secret by Andres AlmirayZeroTurnaround
 
Develop your next app with kotlin @ AndroidMakersFr 2017
Develop your next app with kotlin @ AndroidMakersFr 2017Develop your next app with kotlin @ AndroidMakersFr 2017
Develop your next app with kotlin @ AndroidMakersFr 2017Arnaud Giuliani
 
Дмитрий Нестерук, Паттерны проектирования в XXI веке
Дмитрий Нестерук, Паттерны проектирования в XXI векеДмитрий Нестерук, Паттерны проектирования в XXI веке
Дмитрий Нестерук, Паттерны проектирования в XXI векеSergey Platonov
 
Kotlin hands on - MorningTech ekito 2017
Kotlin hands on - MorningTech ekito 2017Kotlin hands on - MorningTech ekito 2017
Kotlin hands on - MorningTech ekito 2017Arnaud Giuliani
 
Kotlin advanced - language reference for android developers
Kotlin advanced - language reference for android developersKotlin advanced - language reference for android developers
Kotlin advanced - language reference for android developersBartosz Kosarzycki
 
Groovy Grails DevJam Jam Session
Groovy Grails DevJam Jam SessionGroovy Grails DevJam Jam Session
Groovy Grails DevJam Jam SessionMike Hugo
 
ADG Poznań - Kotlin for Android developers
ADG Poznań - Kotlin for Android developersADG Poznań - Kotlin for Android developers
ADG Poznań - Kotlin for Android developersBartosz Kosarzycki
 
The Kotlin Programming Language
The Kotlin Programming LanguageThe Kotlin Programming Language
The Kotlin Programming Languageintelliyole
 
Concurrency in Programming Languages
Concurrency in Programming LanguagesConcurrency in Programming Languages
Concurrency in Programming LanguagesYudong Li
 

La actualidad más candente (20)

Kotlin Developer Starter in Android projects
Kotlin Developer Starter in Android projectsKotlin Developer Starter in Android projects
Kotlin Developer Starter in Android projects
 
Unit testing concurrent code
Unit testing concurrent codeUnit testing concurrent code
Unit testing concurrent code
 
Kotlin
KotlinKotlin
Kotlin
 
Lambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive CodeLambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive Code
 
C# for-java-developers
C# for-java-developersC# for-java-developers
C# for-java-developers
 
Introduction to kotlin
Introduction to kotlinIntroduction to kotlin
Introduction to kotlin
 
Basic Javascript
Basic JavascriptBasic Javascript
Basic Javascript
 
Javascript basic course
Javascript basic courseJavascript basic course
Javascript basic course
 
Clean coding-practices
Clean coding-practicesClean coding-practices
Clean coding-practices
 
Java 8
Java 8Java 8
Java 8
 
AST Transformations: Groovy’s best kept secret by Andres Almiray
AST Transformations: Groovy’s best kept secret by Andres AlmirayAST Transformations: Groovy’s best kept secret by Andres Almiray
AST Transformations: Groovy’s best kept secret by Andres Almiray
 
Migrating to JUnit 5
Migrating to JUnit 5Migrating to JUnit 5
Migrating to JUnit 5
 
Develop your next app with kotlin @ AndroidMakersFr 2017
Develop your next app with kotlin @ AndroidMakersFr 2017Develop your next app with kotlin @ AndroidMakersFr 2017
Develop your next app with kotlin @ AndroidMakersFr 2017
 
Дмитрий Нестерук, Паттерны проектирования в XXI веке
Дмитрий Нестерук, Паттерны проектирования в XXI векеДмитрий Нестерук, Паттерны проектирования в XXI веке
Дмитрий Нестерук, Паттерны проектирования в XXI веке
 
Kotlin hands on - MorningTech ekito 2017
Kotlin hands on - MorningTech ekito 2017Kotlin hands on - MorningTech ekito 2017
Kotlin hands on - MorningTech ekito 2017
 
Kotlin advanced - language reference for android developers
Kotlin advanced - language reference for android developersKotlin advanced - language reference for android developers
Kotlin advanced - language reference for android developers
 
Groovy Grails DevJam Jam Session
Groovy Grails DevJam Jam SessionGroovy Grails DevJam Jam Session
Groovy Grails DevJam Jam Session
 
ADG Poznań - Kotlin for Android developers
ADG Poznań - Kotlin for Android developersADG Poznań - Kotlin for Android developers
ADG Poznań - Kotlin for Android developers
 
The Kotlin Programming Language
The Kotlin Programming LanguageThe Kotlin Programming Language
The Kotlin Programming Language
 
Concurrency in Programming Languages
Concurrency in Programming LanguagesConcurrency in Programming Languages
Concurrency in Programming Languages
 

Similar a Groovy Ast Transformations (greach)

The Future of JVM Languages
The Future of JVM Languages The Future of JVM Languages
The Future of JVM Languages VictorSzoltysek
 
JDays Lviv 2014: Java8 vs Scala: Difference points & innovation stream
JDays Lviv 2014:  Java8 vs Scala:  Difference points & innovation streamJDays Lviv 2014:  Java8 vs Scala:  Difference points & innovation stream
JDays Lviv 2014: Java8 vs Scala: Difference points & innovation streamRuslan Shevchenko
 
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018Codemotion
 
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 WorldBTI360
 
#살아있다 #자프링외길12년차 #코프링2개월생존기
#살아있다 #자프링외길12년차 #코프링2개월생존기#살아있다 #자프링외길12년차 #코프링2개월생존기
#살아있다 #자프링외길12년차 #코프링2개월생존기Arawn Park
 
Scala presentation by Aleksandar Prokopec
Scala presentation by Aleksandar ProkopecScala presentation by Aleksandar Prokopec
Scala presentation by Aleksandar ProkopecLoïc Descotte
 
Scala - en bedre og mere effektiv Java?
Scala - en bedre og mere effektiv Java?Scala - en bedre og mere effektiv Java?
Scala - en bedre og mere effektiv Java?Jesper Kamstrup Linnet
 
Oop2010 Scala Presentation Stal
Oop2010 Scala Presentation StalOop2010 Scala Presentation Stal
Oop2010 Scala Presentation StalMichael Stal
 
JavaScript - new features in ECMAScript 6
JavaScript - new features in ECMAScript 6JavaScript - new features in ECMAScript 6
JavaScript - new features in ECMAScript 6Solution4Future
 
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 GroovyCodecamp Romania
 
Naïveté vs. Experience
Naïveté vs. ExperienceNaïveté vs. Experience
Naïveté vs. ExperienceMike Fogus
 
Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...
Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...
Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...James Titcumb
 
JavaScript Survival Guide
JavaScript Survival GuideJavaScript Survival Guide
JavaScript Survival GuideGiordano Scalzo
 
JavaScript and the AST
JavaScript and the ASTJavaScript and the AST
JavaScript and the ASTJarrod Overson
 

Similar a Groovy Ast Transformations (greach) (20)

The Future of JVM Languages
The Future of JVM Languages The Future of JVM Languages
The Future of JVM Languages
 
JDays Lviv 2014: Java8 vs Scala: Difference points & innovation stream
JDays Lviv 2014:  Java8 vs Scala:  Difference points & innovation streamJDays Lviv 2014:  Java8 vs Scala:  Difference points & innovation stream
JDays Lviv 2014: Java8 vs Scala: Difference points & innovation stream
 
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
 
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
 
#살아있다 #자프링외길12년차 #코프링2개월생존기
#살아있다 #자프링외길12년차 #코프링2개월생존기#살아있다 #자프링외길12년차 #코프링2개월생존기
#살아있다 #자프링외길12년차 #코프링2개월생존기
 
What's New In C# 7
What's New In C# 7What's New In C# 7
What's New In C# 7
 
Scala - en bedre Java?
Scala - en bedre Java?Scala - en bedre Java?
Scala - en bedre Java?
 
Scala presentation by Aleksandar Prokopec
Scala presentation by Aleksandar ProkopecScala presentation by Aleksandar Prokopec
Scala presentation by Aleksandar Prokopec
 
Scala - en bedre og mere effektiv Java?
Scala - en bedre og mere effektiv Java?Scala - en bedre og mere effektiv Java?
Scala - en bedre og mere effektiv Java?
 
Oop2010 Scala Presentation Stal
Oop2010 Scala Presentation StalOop2010 Scala Presentation Stal
Oop2010 Scala Presentation Stal
 
JavaScript - new features in ECMAScript 6
JavaScript - new features in ECMAScript 6JavaScript - new features in ECMAScript 6
JavaScript - new features in ECMAScript 6
 
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
 
Naïveté vs. Experience
Naïveté vs. ExperienceNaïveté vs. Experience
Naïveté vs. Experience
 
Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...
Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...
Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...
 
Introduction to Scala
Introduction to ScalaIntroduction to Scala
Introduction to Scala
 
JavaScript Survival Guide
JavaScript Survival GuideJavaScript Survival Guide
JavaScript Survival Guide
 
Functional Scala 2020
Functional Scala 2020Functional Scala 2020
Functional Scala 2020
 
JavaScript and the AST
JavaScript and the ASTJavaScript and the AST
JavaScript and the AST
 
Scala introduction
Scala introductionScala introduction
Scala introduction
 
Workshop Scala
Workshop ScalaWorkshop Scala
Workshop Scala
 

Más de HamletDRC

Static Analysis and AST Transformations
Static Analysis and AST TransformationsStatic Analysis and AST Transformations
Static Analysis and AST TransformationsHamletDRC
 
Static Analysis in IDEA
Static Analysis in IDEAStatic Analysis in IDEA
Static Analysis in IDEAHamletDRC
 
10 Years of Groovy
10 Years of Groovy10 Years of Groovy
10 Years of GroovyHamletDRC
 
Java Boilerplate Busters
Java Boilerplate BustersJava Boilerplate Busters
Java Boilerplate BustersHamletDRC
 
New Ideas for Old Code - Greach
New Ideas for Old Code - GreachNew Ideas for Old Code - Greach
New Ideas for Old Code - GreachHamletDRC
 
Java Boilerplate Busters
Java Boilerplate BustersJava Boilerplate Busters
Java Boilerplate BustersHamletDRC
 

Más de HamletDRC (6)

Static Analysis and AST Transformations
Static Analysis and AST TransformationsStatic Analysis and AST Transformations
Static Analysis and AST Transformations
 
Static Analysis in IDEA
Static Analysis in IDEAStatic Analysis in IDEA
Static Analysis in IDEA
 
10 Years of Groovy
10 Years of Groovy10 Years of Groovy
10 Years of Groovy
 
Java Boilerplate Busters
Java Boilerplate BustersJava Boilerplate Busters
Java Boilerplate Busters
 
New Ideas for Old Code - Greach
New Ideas for Old Code - GreachNew Ideas for Old Code - Greach
New Ideas for Old Code - Greach
 
Java Boilerplate Busters
Java Boilerplate BustersJava Boilerplate Busters
Java Boilerplate Busters
 

Último

Evaluating the top large language models.pdf
Evaluating the top large language models.pdfEvaluating the top large language models.pdf
Evaluating the top large language models.pdfChristopherTHyatt
 
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.pdfUK Journal
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
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.pptxHampshireHUG
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEarley Information Science
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
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 MenDelhi Call girls
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
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 2024Rafal Los
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfsudhanshuwaghmare1
 
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 interpreternaman860154
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdflior mazor
 
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...Martijn de Jong
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProduct Anonymous
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 

Último (20)

Evaluating the top large language models.pdf
Evaluating the top large language models.pdfEvaluating the top large language models.pdf
Evaluating the top large language models.pdf
 
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
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
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
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
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
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
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
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
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
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
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...
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 

Groovy Ast Transformations (greach)

  • 1. Groovy Code Generation Hamlet D'Arcy – Canoo Engineering AG @HamletDRC http://hamletdarcy.blogspot.com
  • 2.
  • 3.
  • 4. I shot an elephant in my pajamas.
  • 5. Subject: I Verb: shot Direct Object: an elephant Indirect Object: in my pajamas
  • 6. I shot an elephant in my pajamas. How he got in my pajamas, I'll never know.
  • 7. Subject: I Verb: shot an elephant in my pajamas Participle Phrase
  • 8. We invited the strippers, JFK and Stalin
  • 9. We invited the strippers, JFK and Stalin
  • 10. Subject: We Verb: invited Participle: JFK strippers Stalin
  • 11. We invited the strippers, JFK and Stalin
  • 12. Subject: We Verb: invited Participle Phrase: the strippers JFK Stalin
  • 13.
  • 14.
  • 15.
  • 16. public class Person { private String name ; public void setName(String name) { this . name = name;} public String getNameName() { return name ; } public static void main(String[] args) { Person p = new Person(); p.setName( “Hamlet” ); System. out .println(p); } }
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.  
  • 25.  
  • 26.  
  • 27.  
  • 28. // javac private void generatePropertyChangeSupportField(JavacNode node) { if (fieldAlreadyExists( PROPERTY_SUPPORT_FIELD_NAME , node)) return ; JCExpression exp = chainDots(node.getTreeMaker(), node, &quot;this&quot; ); JCVariableDecl fieldDecl = newField() .ofType(PropertyChangeSupport. class ) .withName( PROPERTY_SUPPORT_FIELD_NAME ) .withModifiers( PRIVATE | FINAL ) .withArgs(exp) .buildWith(node); injectField(node, fieldDecl); } // ECJ private void generatePropertyChangeSupportField(EclipseNode node) { if (fieldAlreadyExists( PROPERTY_SUPPORT_FIELD_NAME , node)) return ; Expression exp = referenceForThis(node.get()); FieldDeclaration fieldDecl = newField() .ofType(PropertyChangeSupport. class ) .withName( PROPERTY_SUPPORT_FIELD_NAME ) .withModifiers( PRIVATE | FINAL ) .withArgs(exp) .buildWith(node); injectField(node, fieldDecl); } Alex Ruiz – Custom AST Transformations with Project Lombok http://www.ibm.com/developerworks/java/library/j-lombok/
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.  
  • 39.  
  • 40.  
  • 41.  
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51. How it Works Compiles Java to AST Analyzes AST Nodes Rewrites with Strings
  • 52. (x != null && x instanceof SomeClass)
  • 53. (x != null && x instanceof SomeClass) @Override public void visitBinaryExpression(PsiBinaryExpression exp) { if (exp.token == '==' ) { if (exp.left instanceof BinaryExpression ... ) { if (exp.right instanceof BinaryExpression ... ) { registerError(exp, '...' ) } } } }
  • 54. (x != null && x instanceof SomeClass) @Override public void visitBinaryExpression(PsiBinaryExpression expression) { super.visitBinaryExpression(expression); if (!expression.getOperationTokenType().equals(JavaTokenType.ANDAND)) { return; } PsiExpression lOperand = expression.getLOperand(); PsiExpression rOperand = expression.getROperand(); if (!isBinaryExpressionAndInstanceOfCheck(lOperand, rOperand) && !isBinaryExpressionAndInstanceOfCheck(rOperand, lOperand)) { return; } PsiBinaryExpression binaryExpression = getInnerBinaryExpression(expression); if (!binaryExpression.getOperationTokenType().equals(JavaTokenType.NE)) { return; } boolean leftIsNullCheck = isNullCheck(binaryExpression.getLOperand(), binaryExpression.getROperand()); boolean rightIsNullCheck = isNullCheck(binaryExpression.getROperand(), binaryExpression.getLOperand()); if (!leftIsNullCheck && !rightIsNullCheck) return; PsiReferenceExpression theReference = getReferenceFromNotNullCheck(binaryExpression); PsiExpression instanceOfOperand = getInstanceOfOperand(expression); if (!(instanceOfOperand instanceof PsiReferenceExpression)) { return; } String referenceNameA = (theReference).getReferenceName(); String referenceNameB = ((PsiReferenceExpression)instanceOfOperand).getReferenceName(); if (referenceNameA == null || referenceNameB == null) { return; } if (!referenceNameA.equals(referenceNameB)) { return; } registerError(expression); }
  • 55. Rewrite: (x instanceof SomeClass) PsiElement andand = descriptor.getPsiElement(); if (andand instanceof PsiBinaryExpression) { PsiExpression lhs = ((PsiBinaryExpression)andand).getLOperand(); PsiExpression rhs = ((PsiBinaryExpression)andand).getROperand(); if (lhs instanceof PsiInstanceOfExpression) { replaceExpression((PsiBinaryExpression)andand, lhs.getText()); } else if (rhs instanceof PsiInstanceOfExpression) { replaceExpression((PsiBinaryExpression)andand, rhs.getText()); } }
  • 56. Rewrite: (x instanceof SomeClass) when (descriptor.psiElement) { is PsiBinaryExpression @ ( val lhs is PsiInstanceOfExpression, *) => replaceExpression(descriptor.psiElement, lhs.text) is PsiBinaryExpression @ (*, val rhs is PsiInstanceOfExpression) => replaceExpression(descriptor.psiElement, rhs.text) } Kotlin's “Decomposer patterns”
  • 57. How it Works Compiles Java to AST Analyzes AST Nodes Rewrites with Strings
  • 58.
  • 59. – Dead Code – Defects like GString as Map Key, Duplicate Map Key – Return path analysis (null returns) – Type inference (and improving) – Concurrency Problems (busy wait, etc) – Poor Testing Practices – Un-Groovy code … and 255+ more rules
  • 60. How it works CodeNarc Rule: Ban new java.util.Random() calls @Override void visitConstructorCallExpression( ConstructorCallExpression call) { if (AstUtil.classNodeImplementsType(call. type , Random)) { addViolation(call, 'Using Random is insecure...' ) } super .visitConstructorCallExpression(call) }
  • 64.
  • 65.
  • 66.
  • 67. How it Works Compiles Groovy to AST Analyzes AST Nodes – Not well typed – Not many tokens – unreliable with other AST Transforms
  • 68. Embedded Languages def s = new ArithmeticShell() assert 2 == s.evaluate( ' 1+1 ' ) assert 1.0 == s.evaluate( 'cos(2*PI)' ) public interface GroovyCodeVisitor { void visitBlockStatement(BlockStatement statement) void visitForLoop(ForStatement forLoop) void visitWhileLoop(WhileStatement loop) void visitDoWhileLoop(DoWhileStatement loop) ... }
  • 69.
  • 70.
  • 71.
  • 72.
  • 73. Ruby FizzBuzz 1.upto(100) do |n| print &quot;Fizz&quot; if a = ((n % 3) == 0) print &quot;Buzz&quot; if b = ((n % 5) == 0) print n unless (a || b) print &quot;&quot; end
  • 74. Mirah FizzBuzz 1.upto(100) do |n| print &quot;Fizz&quot; if a = ((n % 3) == 0) print &quot;Buzz&quot; if b = ((n % 5) == 0) print n unless (a || b) print &quot;&quot; end
  • 75. Mirah: Pure JVM Class Output public class Fizz-buzz { public static void main(String[] argv) { ... do { n = __xform_tmp_4; ... if (n % 15 == 0) System.out.println(&quot;FizzBuzz&quot;); else if (n % 5 == 0) System.out.println(&quot;Buzz&quot;); else if (n % 3 == 0) System.out.println(&quot;Fizz&quot;); else System.out.println(n); ... } while (__xform_tmp_4 <= __xform_tmp_5); } }
  • 76. Mirah: .java File Output if (((n % 15) == 0)) { PrintStream temp$10 = System.out; temp$10.println(&quot;FizzBuzz&quot;); } else { if (((n % 5) == 0)) { PrintStream temp$11 = System.out; temp$11.println(&quot;Buzz&quot;); } else { if (((n % 3) == 0)) { PrintStream temp$12 = System.out; temp$12.println(&quot;Fizz&quot;); } else { PrintStream temp$13 = System.out; temp$13.println(n); } } }
  • 77. What it Means – Fast – Lightweight – Uses JDK – Runs on Android? – Runs on GWT?
  • 80.  
  • 81. Mirah Macros macro def eachChar(value, &block) quote { `value`.toCharArray.each do | my_char | `block.body` end } end eachChar('Hola Greach Conference!') do | my_char | puts my_char end
  • 82. Mirah Macros class Person make_attr :firstName, :string end p = Person.new p.firstName = 'hamlet' puts p.firstName
  • 83. Mirah Macros macro def make_attr(name, type) attribute_name = name.string_value() quote { def `name` @`name` end def `&quot;#{attribute_name}_set&quot;`(value:`type`) @`name` = value end } end
  • 84.
  • 85. Difficult to find call-sites
  • 86.
  • 87. Easily quote ASTs into source
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95. – Groovy Wiki and Mailing List is amazingly helpful
  • 96. – Use your creativity and patience
  • 97. – http://hamletdarcy.blogspot.com & @HamletDRC Groovy, Grails, Griffon, and Agile Consulting [email_address] or [email_address]