Introducció

Les Transformacions AST (Abstract Syntax Tree) són una característica avançada de Groovy que permet modificar el codi durant la compilació. Això permet als desenvolupadors afegir comportaments personalitzats, optimitzar el codi i aplicar patrons de disseny de manera automàtica.

Què és un AST?

Un AST és una representació en forma d'arbre de l'estructura sintàctica del codi font. Cada node de l'arbre representa una construcció del llenguatge de programació, com ara expressions, declaracions i blocs de codi.

Tipus de Transformacions AST

Groovy proporciona diversos tipus de transformacions AST que es poden aplicar al codi:

  1. Transformacions AST Estàtiques: S'apliquen durant la compilació i no tenen accés a la informació d'execució.
  2. Transformacions AST Dinàmiques: S'apliquen durant l'execució i poden modificar el comportament del codi en temps real.

Transformacions AST Estàndard

Groovy inclou diverses transformacions AST estàndard que es poden utilitzar directament. Algunes de les més comunes són:

  • @Immutable: Fa que una classe sigui immutable.
  • @Singleton: Converteix una classe en un singleton.
  • @Delegate: Afegeix delegació a una classe.
  • @Lazy: Crea propietats que es calculen només quan es necessiten.

Exemple: Utilitzant @Immutable

import groovy.transform.Immutable

@Immutable
class Person {
    String name
    int age
}

def person = new Person(name: 'John', age: 30)
println person.name // John
println person.age  // 30

// Intentar modificar una propietat llançarà una excepció
// person.name = 'Jane' // Error: Cannot set the property 'name' on an immutable class

Creació de Transformacions AST Personalitzades

A més de les transformacions estàndard, Groovy permet crear transformacions AST personalitzades. Això es fa mitjançant l'ús d'anotacions i classes que implementen la interfície ASTTransformation.

Exemple: Creant una Transformació AST Personalitzada

  1. Definir l'Anotació
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target

@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.METHOD])
@interface LogExecutionTime {}
  1. Implementar la Transformació
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.ast.expr.*
import org.codehaus.groovy.ast.stmt.*
import org.codehaus.groovy.control.*
import org.codehaus.groovy.transform.*

@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
class LogExecutionTimeASTTransformation implements ASTTransformation {
    void visit(ASTNode[] nodes, SourceUnit source) {
        MethodNode methodNode = nodes[1] as MethodNode
        BlockStatement methodBody = methodNode.code as BlockStatement

        // Crear el codi per registrar el temps d'execució
        Statement startTime = new ExpressionStatement(
            new DeclarationExpression(
                new VariableExpression("startTime"),
                Token.newSymbol("=", -1, -1),
                new MethodCallExpression(
                    new ClassExpression(ClassHelper.make(System)),
                    "currentTimeMillis",
                    ArgumentListExpression.EMPTY_ARGUMENTS
                )
            )
        )

        Statement endTime = new ExpressionStatement(
            new MethodCallExpression(
                new ClassExpression(ClassHelper.make(System)),
                "out.println",
                new ArgumentListExpression(
                    new BinaryExpression(
                        new ConstantExpression("Execution time: "),
                        Token.newSymbol("+", -1, -1),
                        new BinaryExpression(
                            new MethodCallExpression(
                                new ClassExpression(ClassHelper.make(System)),
                                "currentTimeMillis",
                                ArgumentListExpression.EMPTY_ARGUMENTS
                            ),
                            Token.newSymbol("-", -1, -1),
                            new VariableExpression("startTime")
                        )
                    )
                )
            )
        )

        // Afegir el codi al cos del mètode
        methodBody.statements.add(0, startTime)
        methodBody.statements.add(endTime)
    }
}
  1. Utilitzar l'Anotació
class Example {
    @LogExecutionTime
    void longRunningMethod() {
        Thread.sleep(1000)
    }
}

def example = new Example()
example.longRunningMethod()
// Output: Execution time: 1000 (aproximadament)

Exercicis Pràctics

Exercici 1: Utilitzar Transformacions AST Estàndard

  1. Crea una classe Book amb les propietats title i author.
  2. Fes que la classe sigui immutable utilitzant l'anotació @Immutable.
  3. Intenta modificar una propietat després de crear una instància i observa el resultat.

Exercici 2: Crear una Transformació AST Personalitzada

  1. Defineix una anotació @ToString.
  2. Implementa una transformació AST que afegeixi un mètode toString a qualsevol classe anotada amb @ToString.
  3. El mètode toString ha de retornar una cadena amb els noms i valors de totes les propietats de la classe.

Solucions

Solució Exercici 1

import groovy.transform.Immutable

@Immutable
class Book {
    String title
    String author
}

def book = new Book(title: 'Groovy in Action', author: 'Dierk König')
println book.title  // Groovy in Action
println book.author // Dierk König

// Intentar modificar una propietat llançarà una excepció
// book.title = 'New Title' // Error: Cannot set the property 'title' on an immutable class

Solució Exercici 2

  1. Definir l'Anotació
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target

@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.TYPE])
@interface ToString {}
  1. Implementar la Transformació
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.ast.expr.*
import org.codehaus.groovy.ast.stmt.*
import org.codehaus.groovy.control.*
import org.codehaus.groovy.transform.*

@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
class ToStringASTTransformation implements ASTTransformation {
    void visit(ASTNode[] nodes, SourceUnit source) {
        ClassNode classNode = nodes[1] as ClassNode

        // Crear el mètode toString
        MethodNode toStringMethod = new MethodNode(
            "toString",
            Modifier.PUBLIC,
            ClassHelper.STRING_TYPE,
            Parameter.EMPTY_ARRAY,
            ClassNode.EMPTY_ARRAY,
            new BlockStatement(
                [new ReturnStatement(
                    new ConstantExpression(
                        classNode.properties.collect { prop ->
                            "${prop.name}=${prop.name}"
                        }.join(", ")
                    )
                )],
                new VariableScope()
            )
        )

        // Afegir el mètode a la classe
        classNode.addMethod(toStringMethod)
    }
}
  1. Utilitzar l'Anotació
@ToString
class Person {
    String name
    int age
}

def person = new Person(name: 'John', age: 30)
println person.toString() // name=John, age=30

Conclusió

Les transformacions AST són una eina poderosa en Groovy que permeten modificar el codi durant la compilació per afegir funcionalitats, optimitzar el rendiment i aplicar patrons de disseny de manera automàtica. Amb les transformacions AST, els desenvolupadors poden crear codi més net, mantenible i eficient.

© Copyright 2024. Tots els drets reservats