📜 ⬆️ ⬇️

AST Transformations - First Step to Heavy Substances

And let's do magic with your Java code. Here is this:


We take it:
import groovy.transform.Canonical import groovy.transform.TupleConstructor @Canonical @TupleConstructor class Person { int id String firstName String lastName Date birthdate } 

We compile, and in bytecode we get an analogue of this one:
Hellish boilerplate on Java for over 100 lines
 import java.util.Date; import java.util.Map; public class Person { private int id; private String firstName; private String lastName; private Date birthdate; //   @TupleConstructor- public Person(Map parameters){ this.id = (int) parameters.get("id"); this.firstName = (String) parameters.get("firstName"); this.lastName = (String) parameters.get("lastName"); this.birthdate = (Date) parameters.get("birthdate"); } public Person(int id, String firstName, String lastName, Date birthdate) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.birthdate =birthdate; } public Person(int id, String firstName, String lastName) { this(id, firstName, lastName, null); } public Person(int id, String firstName) { this(id, firstName, null, null); } public Person(int id) { this(id, null, null, null); } public Person() { this(0, null, null, null); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; if (id != person.id) return false; if (birthdate != null ? !birthdate.equals(person.birthdate) : person.birthdate != null) return false; if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) return false; if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null) return false; return true; } @Override public int hashCode() { int result = id; result = 31 * result + (firstName != null ? firstName.hashCode() : 0); result = 31 * result + (lastName != null ? lastName.hashCode() : 0); result = 31 * result + (birthdate != null ? birthdate.hashCode() : 0); return result; } @Override public String toString() { return "Person{" + "id=" + id + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", birthdate=" + birthdate + '}'; } public int getId() { return this.id; } public void setId(int paramInt) { this.id = paramInt; } public String getFirstName() { return this.firstName; } public void setFirstName(String paramString) { this.firstName = paramString; } public String getLastName() { return this.lastName; } public void setLastName(String paramString) { this.lastName = paramString; } public Date getBirthdate() { return this.birthdate; } public void setBirthdate(Date paramDate) { this.birthdate = paramDate; } } 



Well, yes, it's nice. But nothing unique, here is Lombok , not to mention the ability of any good IDE to first generate and then hide all this boilerplate.
')
So why exactly Groovy, why AST transformations?
In this article I will try to justify in the short statement why to use Groovy AST transformations in Java projects, and (again, in brief) to tell what AST transfromations is in Groovy today. If you already know why, and want only "how and what", feel free to scroll to "Introduction to AST transformations".

So why AST transformations and not Lombok?


To begin with, in order to use AST transformations, you do not need to know Groovy, write on Groovy, or run Groovy in runtime. The transformation occurs during the compilation of the sorts and Groovy is added by one jar to the list of dependencies. Everything.
Thus, AST transformations are an excellent way to “push” Groovy into your project: “Look, boss, this is okay, this is just another library to fight the boiler back!”. And then, already, of course, step by step, a test on Spock, behind the build on Graydle, the real Groovy will appear in your code - dynamic, functional and elegant. AST transformations are just the first step.
In addition, AST transformations are much more extensible, powerful and versatile than Lombok.
Last but not least, AST transformations are perfectly supported in any IDE with Groovy support, and not just in Eclipse.
Lombok’s frontal comparison with AST transformations is clearly beyond the scope of this article, so let's stop here.

It seems to me to be taken for granted that the generation of bytecode has a huge advantage over the generation (and then “collapsing” in the editor so as not to put off an eye) of the source code - the generation occurs during compilation, it does not need to be “maintained”. One example - IntelliJ IDEA perfectly generates hashcode and equals. When adding a new field, I use these pens to erase these 2 methods and generate them again. Fu

There is much more to say about the benefits of AST transformations for both Java and Groovy developers, but I hope the idea is clear. It's time to move on to practice.

Introduction to AST transformations


One of Groovy's most important advantages is, of course, metaprogramming . It comes in two types - at compile time and at run time.
Metaprogramming during execution is about “ah, did you call a method that doesn't exist? No problem, we’ll think of something now, based on what you had in mind when you called this method. ” Examples of such a mass - almost any Groovy library is based on such things, whether it be builders, slarpers, Grails, Ratpack, Gradle, and everything else. But now it’s not about that (if you want it, see point 1 of brazen PR at the end of the post).

Now we will talk about metaprogramming during compilation, namely, how to simply write one in the code, to get another in bytecode (well, or additional).

We begin with the transformation that is stitched right in Groovy itself, without any annotations and other additions.
We write:
 class Person { String name } 

The output is a bytecode, in which all fields are private (in this case, name ), and all getters and setters are written (well, in this case, only getName() and setName(String name) , but the idea is clear).

This beautiful little thing is a complete example of metaprogramming at compile time.

Looking at this small escape from the boilerplate, the wonderful man Danno Ferrin said to himself: “But there are still a lot of boilerplate, besides the getters and setters, and not everyone has the same! Let's think of something that is connected and extensible! ”And so the AST Transformations were born (the first, oddly enough, was @Bindable . Although, if you look at how much code it throws, it may not be strange).

AST transformations are a set of annotations that change the abstract syntax tree on the fly during Groovy compilation. We can say that adding getters and setters is an embedded AST transformation that always works, without adding annotations. The rest is included only on demand.

Let's see what we have:

And that is not all! There is also an arch-important @CompileStatic , @Field , and a whole set of annotations for the endowment of suffering for concurency, but this is, all the same, another time (well, or, see point 1 of brazen PR at the end of the post).

PS Now that you know what it is about, here are two interesting Habra articles about how and what to write new AST transformations. About the same look below, in paragraph 2 of the brazen PR.

And now brazen PR of 2 points:
  1. Who needs Gruvey from scratch to sufficiently advanced support, let's go to my trainings, April 17th in Moscow and April 15th in Kazan (knock alexbel )
  2. To whom are the abstract syntax tree dismemberments and writing your own AST transformations to fight your own cockroaches with boilerplays, go to my reports at the JPoint on April 18th and on JavaDay Kazan on April 16th (knock alexbel again)

Source: https://habr.com/ru/post/215343/


All Articles