techktalk #12 grokking: writing code that writes code – nguyen luong

39
1/39 Writing code that writes code by LUONG Hoang Nguyen Senior Java Technical Lead ekino .

Upload: grokking-vn

Post on 27-Jan-2017

114 views

Category:

Technology


6 download

TRANSCRIPT

Page 1: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

1/39

Writing codethat writes codebyLUONG Hoang NguyenSenior Java Technical Lead

ekino.

Page 2: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

2/392/39

1. Context

2. Project Lombok

3. Alternatives

4. Conclusion

AGENDA

Page 3: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

3/39

1. Context

Page 4: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

4/394/39

Developer jobAs programmers, we often find ourselves in a similar

position.

We need to achieve thesame functionality, but in different contexts.

We need to repeat information in differentplaces.

Sometimes we just need to protect ourselves from carpal tunnel syndrome bycutting down on repetitive typing

Page 5: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

5/395/39

Boiler Plate CodeSyntax Verbosity

Problems of the Industry

Non StandardizationNot following the standard code conventions

Human Error/DependencyRepeatable things (defining Getters,

Setters etc.)

Page 6: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

6/39

2. Project Lombok

Page 7: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

7/39

WHAT IS IT ?

Page 8: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

8/398/39

•@Getter / @Setter

•@ToString

•@EqualsAndHashCode

•@Data

•@NoArgsConstructor

•@RequiredArgsConstructor

•@AllArgsConstructor

•@Cleanup

•@Synchronized

•@Slf4j

•@Builder

•val

Features

Page 9: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

9/399/39

@Getter / Setter

Features • Automatic Generation of getters and setters.

• Method will be public unless you explicitly specify an AccessLevel

• @Getter and/or @Setter annotation on class

• Disable getter/setter generation for any field by using the special AccessLevel.NONE

• @NotNull for nullity check

Page 10: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

10/3910/39

import lombok.AccessLevel;import lombok.Getter;import lombok.Setter;

public class GetterSetterExample { /** * Age of the person. Water is wet. * * @param age New value for this person's age. Sky is blue. * @return The current value of this person's age. Circles are round. */ @Getter @Setter private int age = 10;

/** * Name of the person. * -- SETTER -- * Changes the name of this person. * * @param name The new value. */ @Setter(AccessLevel.PROTECTED) private String name;

@Override public String toString() { return String.format("%s (age: %d)", name, age); }}

public class GetterSetterExample { /** * Age of the person. Water is wet. * * @param age New value for this person's age. Sky is blue. * @return The current value of this person's age. Circles are round. */ private int age = 10;

/** * Name of the person. * -- SETTER -- * Changes the name of this person. * * @param name The new value. */ private String name;

@Override public String toString() { return String.format("%s (age: %d)", name, age); }

public int getAge() { return this.age; }

public void setAge(int age) { this.age = age; }

protected void setName(String name) { this.name = name; }}

Page 11: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

11/3911/39

@EqualsAndHashCode

Features • Generates hashCode and equals implementations from the fields (non-static, non-transient)

• Parameters

Page 12: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

12/3912/39

import lombok.EqualsAndHashCode;

@EqualsAndHashCode(exclude = { "id", "shape" })class EqualsAndHashCodeExample { private transient int transientVar = 10; private String name; private double score; private Shape shape = new Square(5, 10); private String[] tags; private int id;

public String getName() { return this.name; }

}

public class EqualsAndHashCodeExample { private transient int transientVar = 10; private String name; private double score; private Shape shape = new Square(5, 10); private String[] tags; private int id; public String getName() { return this.name; }

public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof EqualsAndHashCodeExample)) { return false; } final EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o; if (!other.canEqual((Object) this)) { return false; } final Object this$name = this.getName(); final Object other$name = other.getName(); if (this$name == null ? other$name != null : !this$name.equals(other$name)) { return false; } if (Double.compare(this.score, other.score) != 0) { return false; } if (!java.util.Arrays.deepEquals(this.tags, other.tags)) { return false; } return true; }

public int hashCode() { final int PRIME = 59; int result = 1; final Object $name = this.getName(); result = result * PRIME + ($name == null ? 0 : $name.hashCode()); final long $score = Double.doubleToLongBits(this.score); result = result * PRIME + (int) ($score >>> 32 ^ $score); result = result * PRIME + java.util.Arrays.deepHashCode(this.tags); return result; }

protected boolean canEqual(Object other) { return other instanceof EqualsAndHashCodeExample; }}

Page 13: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

13/3913/39

@Data

Features • All together now: A shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, @Setter on all non-final fields, and @RequiredArgsConstructor

• Included annotations with changed parameters can be defined along with @Data

Page 14: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

14/3914/39

import lombok.Data;

@Datapublic class DataExample { private final String name; private double score;}

public class DataExample { private final String name; private double score; @java.beans.ConstructorProperties({ "name" }) public DataExample(String name) { this.name = name; } public String getName() { return this.name; } public double getScore() { return this.score; } public void setScore(double score) { this.score = score; } public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof DataExample)) { return false; } final DataExample other = (DataExample) o; if (!other.canEqual((Object) this)) { return false; } final Object this$name = this.name; final Object other$name = other.name; if (this$name == null ? other$name != null : !this$name.equals(other$name)) { return false; } if (Double.compare(this.score, other.score) != 0) { return false; } return true; } public int hashCode() { final int PRIME = 59; int result = 1; final Object $name = this.name; result = result * PRIME + ($name == null ? 0 : $name.hashCode()); final long $score = Double.doubleToLongBits(this.score); result = result * PRIME + (int) ($score >>> 32 ^ $score); return result; } protected boolean canEqual(Object other) { return other instanceof DataExample; } public String toString() { return "DataExample(name=" + this.name + ", score=" + this.score + ")"; }}

Page 15: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

15/3915/39

@NoArgsConstructor

@RequiredArgsConstructor

@AllArgsConstructor

Features • Generates constructors that take no arguments, one argument per final / non-null field, or one argument for every field.

• Specialized constructor can be made. Compile-time error in case of any conflicts.

Page 16: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

16/3916/39

@Cleanup

Features • Automatic resource management: Call your close() methods safely with no hassle.

@Cleanup InputStream in=new FileInputStream("some/file");

• If the type of object you'd like to cleanup does not have a close() method, but some other no-argument method like dispose() so write it as

@Cleanup("dispose") TestClass t = new TestClass();

Page 17: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

17/3917/39

@Builderval

Features • @Builder generates a static builder() method that returns a builder instance. This builder instance can be used to build an object of the class annotated with @Builder (here Person)

• Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build();

• val: You can use val as the type of a local variable declaration instead of actually writing the type. This feature works on local variables and on foreach loops only, not on fields

val example = new ArrayList<String>(); example.add("Hello, World!");

Page 18: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

18/3918/39

import lombok.Builder;

@Builderpublic class BuilderExample { private String name; private int age;}

public class BuilderExample { private String name; private int age;

@java.beans.ConstructorProperties({ "name", "age" }) BuilderExample(String name, int age) { this.name = name; this.age = age; }

public static BuilderExampleBuilder builder() { return new BuilderExampleBuilder(); }

public static class BuilderExampleBuilder { private String name; private int age;

BuilderExampleBuilder() { }

public BuilderExample.BuilderExampleBuilder name(String name) { this.name = name; return this; }

public BuilderExample.BuilderExampleBuilder age(int age) { this.age = age; return this; }

public BuilderExample build() { return new BuilderExample(name, age); }

public String toString() { return "BuilderExampleBuilder(name=" + this.name + ", age=" + this.age + ")"; } }}

Page 19: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

19/3919/39

• Generates Java Boilerplate

• Compile Time Only -> not affect performance for runtime• For Eclipse and javac• IntelliJ IDEA & NetBeans too

• Removable with delombok

• Know what is generated

Features

Page 20: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

20/3920/39

How does it work? Java compilation has 3 stages: 

In the Parse and Enter phase, the compiler parses source files into an Abstract Syntax Tree (AST

In the Annotation Processing phase, custom annotation processors are invoked Annotation processors can do things like validate classes or generate new resources, including source files.

In the last phase, Analyse and Generate, the compiler generates class files (byte code) from the Abstract Syntax Trees generated in phase 1

Project Lombok hooks itself into the compilation process as an annotation processor. Normally, annotation processors only generate new source files whereas Lombok modifies existing classes. 

Page 21: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

21/3921/39

Example • Example we create a very simple Lombok transformation that adds a helloWorld method to any class annotated as @HelloWorld.

• The basic classes we need to write are:- Annotation class- Eclipse handler- Javac handler (Intellj also supported this)

@HelloWorldpublic class MyClass {

}

public class MyClass {public void helloWorld() { System.out.println("Hello World"); }}

Page 22: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

22/3922/39

Annotation

Example • At first, we just need to create an annotation called HelloWorld that can be applied to classes.

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;

@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE)public @interface HelloWorld {}

Page 23: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

23/3923/39

Handler

Example • Our handler will be adding a new Method Declaration to the Type Declaration. A Method Declaration is composed of several components. The AST for our Method Declaration will have this :

Page 24: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

24/3924/39

Handler Logic

Example • Our handle method will need to do the following:

1. Mark annotation as processed

2. Create the helloWorld method

3. Inject the helloWorld method into the AST of the annotated class

Page 25: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

25/3925/39

Lombok provides our handler with the AST of the annotation node. We know that the annotation can only be applied to a Type. To get the AST for the Type (Class), we call annotationNode.up(). The annotation is a child of the Type AST, so by calling up() we get the parent AST which is the Type AST we need to modify.

Javac Handler

Example

@ProviderFor(JavacAnnotationHandler.class)public class HandleHelloWorld implements JavacAnnotationHandler<HelloWorld>{

public boolean handle(AnnotationValues<HelloWorld> annotation, JCAnnotation ast, JavacNode annotationNode) { JavacHandlerUtil.markAnnotationAsProcessed(annotationNode, HelloWorld.class); JavacNode typeNode = annotationNode.up(); if(notAClass(typeNode)) { annotationNode.addError("@HelloWorld is only supported on a class."); return false; } JCMethodDecl helloWorldMethod = createHelloWorld(typeNode); JavacHandlerUtil.injectMethod(typeNode, helloWorldMethod); return true; }…}

Page 26: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

26/3926/39

• Start with a method node.• Add the return type, parameters, access level, throw clause, etc

to the method node.• Create an expression statement to represent

System.out.println("Hello World")• Add the expression to the method node.

Javac Handler

Hello world method

Example

private JCMethodDecl createHelloWorld(JavacNode type) { TreeMaker treeMaker = type.getTreeMaker(); JCModifiers modifiers = treeMaker.Modifiers(Modifier.PUBLIC); List<JCTypeParameter> methodGenericTypes = List.<JCTypeParameter>nil(); JCExpression methodType = treeMaker.TypeIdent(TypeTag.VOID); Name methodName = type.toName("helloWorld"); List<JCVariableDecl> methodParameters = List.<JCVariableDecl>nil(); List<JCExpression> methodThrows = List.<JCExpression>nil();

JCExpression printlnMethod = JavacHandlerUtil.chainDots(treeMaker, type, "System", "out", "println"); List<JCExpression> printlnArgs = List.<JCExpression>of(treeMaker.Literal("hello world")); JCMethodInvocation printlnInvocation = treeMaker.Apply(List.<JCExpression>nil(), printlnMethod, printlnArgs); JCBlock methodBody = treeMaker.Block(0, List.<JCStatement>of(treeMaker.Exec(printlnInvocation)));

JCExpression defaultValue = null;

return treeMaker.MethodDef( modifiers, methodName, methodType, methodGenericTypes, methodParameters, methodThrows, methodBody, defaultValue );}

Page 27: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

27/3927/39

@UUIDColumn@JsonColumn

Custom Annotations

• We can create custom annotations that helpful for our projects. It will make standardization for our coding.

@Entity@Datapublic class TestModelEntity {@Id@UUIDColumnprivate UUID id;

@JsonColumnprivate Model model;

….}

@Entitypublic class TestModelEntity {@Id@Type(type = "pg-uuid")@GeneratedValue(generator = "uuid2")private UUID id;@Type(type = "JSONB",parameters = { @Parameter(name = "class",value = "com.ekino.lombok.sample.Model")})private Model model;….}

Page 28: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

28/39

3. Alternatives

Page 29: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

29/3929/39

Google AutoAutoFactory

JSR-330-compatible factories

AutoService Provider-configuration files for ServiceLoader

AutoValueImmutable value-type code generation for Java

1.6+ .

CommonHelper utilities for writing annotation

processors.

Page 30: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

30/3930/39

AutoValue

• You write an abstract class

• It has abstract accessors, but no fields

• Annotate it with @AutoValue

• Javac generates a concrete subclass for you

• Callers only ever see the parent type

Google Auto

Page 31: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

31/3931/39

AutoValue

• What you write (plain Java).

import com.google.auto.value.AutoValue;

@AutoValuepublic abstract class Foo { public static Foo create(String text, int number) { // defensive copies, preconditions return new AutoValue_Foo(text, number); }

/** Documentation here. */ public abstract String text(); // or getText(), if you like

/** Documentation here. */ public abstract int number();}

Google Auto

Page 32: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

32/3932/39

AutoValue

• What code does that generate?

final class AutoValue_Foo extends Foo { // (note: NOT public!) private final String text; private final int number;

AutoValue_Foo(String text, int number) { if (text == null) { throw new NullPointerException("text"); } this.text = text; this.number = number; }

@Override public String text() { return text; }

@Override public int number() { return number; }

// and the customary equals/hashCode/toString.}

Google Auto

Page 33: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

33/39

3. Conclusion

Page 34: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

34/3934/39

Advantages

• User writes only plain old Java code

• No runtime impact• no dependency (@AutoValue has source retention)

• No magical modifying of existing classes

• Still just a single javac pass to compile!

Google Auto

Page 35: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

35/3935/39

Disadvantages

• Bootstrap can be annoying—when you first type new AutoValue_Foo it may go red in the IDE

• Some once-per-project build setup required

• Not support setter for AutoValue

• AutoValue does introduce some fragility.• The generator has to choose the order of constructor

parameters somehow, so it uses the order in which the accessors appear in the source file.

• This means an innocent refactoring to reorder those accessors could break your tests

Google Auto

Page 36: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

36/3936/39

Advantages

• User writes only plain old Java code

• No runtime impact• no dependency (annotations has source retention)

• Support delombok

• Maximally concise value classes.

• There is plugin support to integrate with IDE like Lombok plugin for Intellij

Project Lombok

Page 37: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

37/3937/39

Disadvantages

• The compiler hacks are non-standard and fragile.

• Somebody may mention that the code is no longer really Java – the code lose its WYSIWYG feel.

Project Lombok

Page 39: TechkTalk #12 Grokking: Writing code that writes code – Nguyen Luong

39/3939/39

Thank you• LUONG Hoang Nguyen

• Email: [email protected]

• Phone: 0914 974197