Pular para o conteúdo principal

Java

Hook para transformar código Java

schema-version: v3
kind: plugin
metadata:
name: code-transformation-plugin
display-name: code-transformation-plugin
description: Exemplo de transformação de código Java.
version: 1.0.0
spec:
hooks:
- type: code-transformation
engine-version: 0.2.0-beta
trigger: after-render
language: java
trans-pattern-path: trans-patterns/pattern.java
source-path: src/code.java

Construções Suportadas no Java

importDeclaration

As declarações de import têm suporte aos operadores de inserção, remoção e reticências. Isso significa que, em qualquer local que seja válido a existência de uma declaração de import, é possível utilizar qualquer um dos operadores suportados.

O exemplo a seguir insere uma declaração de import a java.util.ArrayList; no lugar da declaração de import a java.util.LinkedList;, independente do que vier antes ou depois de import java.util.LinkedList;. O padrão tem escopo de arquivo.

<<<< pattern-name: change-import-arraylist-to-linked-list; pattern-scope: file-scope; >>>>

<...>
-<import java.util.LinkedList;>-
+<import java.util.ArrayList;>+
<...>

typeDeclaration

Declarações de tipo permitem as seguintes construções em Java:

  • Declaração de Classe
  • Declaração de Interface
  • Declaração de Enum
  • Declaração de Annotation
  • Declaração de Record

As declarações de tipo têm suporte aos operadores de inserção, remoção e reticências. Isso significa que, em qualquer local que seja válido a existência de uma declaração de tipo, é possível utilizar qualquer um dos operadores suportados.

O exemplo a seguir insere uma declaração de enum antes da declaração de uma classe cujo nome é MyClass, independente do que vier antes da classe e do corpo da classe em si. O padrão tem escopo de arquivo.

<<<< pattern-name: modify-java-code; pattern-scope: file-scope; >>>>

<...>

+<public enum MyEnum {
ITEM_1,
ITEM_2,
ITEM_3,
}>+

public class MyClass {
<...>
}

<...>

classBodyDeclaration

Declarações no corpo de classes permitem as seguintes construções em Java:

  • Declaração de Método
  • Declaração de Atributo
  • Declaração de Construtor
  • Declaração de Bloco Estático
  • Declaração de Classe Interna
  • Declaração de Enum Interno
  • Declaração de Record Interno
  • Declaração de Interface Interna
  • Declaração de Annotation Interna

As declarações no corpo de classes têm suporte aos operadores de inserção, remoção e reticências. Isso significa que, em qualquer local que seja válido a existência de uma declaração no corpo de classes, é possível utilizar qualquer um dos operadores suportados.

O exemplo a seguir insere a declaração de atributo private int age; no início do corpo de uma classe cujo nome é Person e também insere os métodos getAge e setAge no fim do corpo da mesma classe, independente do que há no corpo da classe. O padrão tem escopo de arquivo.

<<<< pattern-name: modify-person-class; pattern-scope: file-scope; >>>>

<...>
public class Person {
+<private int age;>+

<...>

+<public int getAge() {
return age;
}>+

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

interfaceBodyDeclaration

Declarações no corpo de interfaces permitem declarar as seguintes construções em Java:

  • Declaração de Método
  • Declaração de Constante
  • Declaração de Classe Interna
  • Declaração de Enum Interno
  • Declaração de Record Interno
  • Declaração de Interface Interna
  • Declaração de Annotation Interna

As declarações no corpo de interfaces têm suporte aos operadores de inserção, remoção e reticências. Isso significa que, em qualquer local que seja válido a existência de uma declaração no corpo de interfaces, é possível utilizar qualquer um dos operadores suportados.

O exemplo a seguir remove a declaração do método executeService do corpo de uma interface cujo nome é Service e insere no lugar o método execute independente do que há no corpo da interface. O padrão tem escopo de arquivo.

<<<< pattern-name: modify-person-class; pattern-scope: file-scope; >>>>

<...>
public interface Service {
<...>
-<public void executeService();>-
+<public void execute();>+
<...>
}
<...>

identifier

Identificadores representam nomes de variáveis, funções, métodos, classes, pacotes, etc. Os identificadores têm suporte aos operadores de ID match, inserção e remoção. Isso significa que no lugar de um identificador é possível utilizar qualquer um dos operadores suportados.

Atenção!

Os operadores de inserção e remoção devem sempre ser utilizados em pares quando aplicados no contexto de um identificador. Isso significa que, sempre que um identificador for removido, outro deve ser inserido em seu lugar. Caso contrário, elementos como classes, métodos ou declarações de variáveis podem acabar sem nome ou com múltiplos nomes, o que pode causar inconsistências no código.

O exemplo a seguir insere um comando de log LOGGER.debug("Starting repo method"); no início de todos os métodos public com retorno void ou Entity de uma classe com qualquer nome (identificador), além de inserir um atributo com o objeto de logger e os import necessários. Para isso usamos três padrões de transformação, o primeiro com escopo de arquivo e os outros dois com escopo de snippet.

<<<< pattern-name: insert-log-support; pattern-scope: file-scope; >>>>

+<import org.apache.log4j.Logger;>+
+<import org.apache.logging.log4j.LogManager;>+

<...>

public class *<.*>* { // id match operator as an identifier to match with a classe with any name
+<private static final Logger LOGGER = LogManager.getLogger(this.getClass());>+
<...>
}

<...>

<<<< pattern-name: insert-log-void-methods; pattern-scope: snippet-scope; >>>>

public void *<.*>*(<...>) { // matches a method with any name
+<LOGGER.debug("Starting repo method");>+
<...>
}

<<<< pattern-name: insert-log-entity-return-methods; pattern-scope: snippet-scope; >>>>

public Entity *<.*>*(<...>) { // matches a method with any name
+<LOGGER.debug("Starting repo method");>+
<...>
}

stringLiteral

Literais de string têm suporte aos operadores de String match, inserção e remoção. Isso significa que, no lugar de uma literal de string, é possível utilizar qualquer um dos operadores suportados.

Atenção!

Note que o operador de inserção e remoção devem sempre ocorrer em par quando estiverem no contexto de um literal de string, ou seja, sempre que houver uma remoção deve haver uma inserção de literal de string.

O exemplo a seguir altera a mensagem de log em todas as chamadas a LOGGER.debug() independente da string que for passada como parâmetro. O padrão tem escopo de snippet.

<<<< pattern-name: change-log-message; pattern-scope: snippet-scope; >>>>

LOGGER.debug(-<*<".*">*>- +<"My new debug message">+); // matches with any string

parameters and arguments

Os parâmetros ocorrem nas declarações e or argumentos nas chamadas, de método, por exemplo. Os parâmetros e argumentos têm suporte aos operadores de inserção, remoção e reticências. Isso significa que, em qualquer local que seja válido a existência de parâmetros ou argumentos, é possível utilizar qualquer um dos operadores suportados.

Atenção!

Parâmetros e argumentos são construções separadas por vírgula. Para utilizar os operadores nesse tipo de construção é recomendado consultar a seção sobre Uso de operadores em construções separadas por vírgula.

O exemplo a seguir troca o parâmetro id no construtor de uma classe de int para long, além de fazer as demais alterações necessárias no atributo id em si e nos métodos get e set. Para isso usamos quatro padrões de transformação, todos com escopo de snippet.

<<<< pattern-name: change-construtor-param; pattern-scope: snippet-scope; >>>>

public *<.*>*(-<int id>- +<long id>+ <...>){ // using operators in parameters
<...>
}

<<<< pattern-name: change-get-method; pattern-scope: snippet-scope; >>>>

-<public int getId(){
return this.id;
}>-

+<public long getId(){
return this.id;
}>+

<<<< pattern-name: change-set-method; pattern-scope: snippet-scope; >>>>

-<public void setId(int id){
this.id = id;
}>-

+<public void setId(long id){
this.id = id;
}>+

<<<< pattern-name: change-field; pattern-scope: snippet-scope; >>>>

-<private Integer id;>-
-<private Long id;>-

O exemplo a seguir insere um novo parâmetro no fim da lista de parâmetros da declaração de um método e também insere um movo argumento em todas as chamadas a esse método. Para isso usamos dois padrões de transformação, todos com escopo de snippet.

<<<< pattern-name: insert-param-method-declaration; pattern-scope: snippet-scope; >>>>

public static void myMethod(<...> +<int param2>+){
<...>
}

<<<< pattern-name: insert-arg-method-call; pattern-scope: snippet-scope; >>>>

myMethod(<...> +<1000>+);

annotation

As annotations têm suporte aos operadores de inserção, remoção e reticências. Isso significa que, em qualquer local que seja válido a existência de annotations, é possível utilizar qualquer um dos operadores suportados.

O exemplo a seguir migra as annotations @Test do JUnit 4, que admitem parâmetros, para as do JUnit 5, que não admitem parâmetros. Para isso usamos dois padrões de transformação, um com escopo de arquivo e outro com escopo de snippet.

<<<< pattern-name: migrate-import-test-annotation-junit-4-to-5; pattern-scope: file-scope; >>>>

<...>
-<import org.junit.Test;>-
+<import org.junit.jupiter.api.Test;>+
<...>

<<<< pattern-name: migrate-test-annotation-junit-4-to-5; pattern-scope: snippet-scope; >>>>

-<@Test(<...>)>-
+<@Test>+

statement

Um statement é um comando da linguagem que produz um efeito colateral e não se reduz a um valor. Esse é o tipo de construção mais comum em Java. A seguir alguns exemplos de statements:

  • Comandos de decisão (if e switch)
  • Comandos de iteração (for, while e do-while)
  • Comando try-catch-finally
  • Comando synchronized
  • Comandos return, yield, break e continue
  • Atribuição de variável
  • Chamada de método

As statements têm suporte aos operadores de inserção, remoção e reticências. Isso significa que, em qualquer local que seja válido a existência de statements, é possível utilizar qualquer um dos operadores suportados.

O exemplo a seguir insere um comando if que checa se o parâmetro dos métodos que iniciam com o nome print e possuem um parâmetro message do tipo String é null e lança uma exceção, caso positivo. Para isso usamos um padrão de transformação com escopo de snippet.

<<<< pattern-name: check-null-parameter; pattern-scope: snippet-scope; >>>>

public void *<print.*>*(String message){
+<if(message == null){
throw new IllegalArgumentException("The message cannot be null.");
}>+
<...>
}