Skip to main content

Java

Hook for Transforming Java Code

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

Supported Constructs in Java

importDeclaration

Import declarations support insertion, removal, and ellipsis operators. You can use any supported operators wherever an import declaration is valid.

The following example replaces the import declaration for java.util.LinkedList; with java.util.ArrayList;, regardless of what comes before or after import java.util.LinkedList;. The pattern has a file scope.

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

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

typeDeclaration

Type declarations allow the following constructs in Java:

  • Class Declaration
  • Interface Declaration
  • Enum Declaration
  • Annotation Declaration
  • Record Declaration

Type declarations support insertion, removal, and ellipsis operators. You can use any supported operators wherever a type declaration is valid.

The following example inserts an enum declaration before a class named MyClass, regardless of what comes before the class or its body. The pattern has a file scope.

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

<...>

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

public class MyClass {
<...>
}

<...>

classBodyDeclaration

Declarations in the class body allow the following constructs in Java:

  • Method Declaration
  • Attribute Declaration
  • Constructor Declaration
  • Static Block Declaration
  • Inner Class Declaration
  • Inner Enum Declaration
  • Inner Record Declaration
  • Inner Interface Declaration
  • Inner Annotation Declaration

Declarations in the class body support insertion, removal, and ellipsis operators. It means any place where a valid declaration in the class body can use any supported operators.

The following example inserts the attribute declaration private int age; at the beginning of the body of a class named Person and also inserts the methods getAge and setAge at the end of the same class, regardless of what is in the class body. The pattern has file scope.

<<<< 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

Declarations in the body of interfaces allow you to declare the following constructs in Java:

  • Method Declaration
  • Constant Declaration
  • Inner Class Declaration
  • Inner Enum Declaration
  • Inner Record Declaration
  • Inner Interface Declaration
  • Inner Annotation Declaration

Declarations in the body of interfaces support the insertion, removal, and ellipsis operators. It means that any of the supported operators can be used where a declaration is valid in the body of an interface.

The following example removes the declaration of the executeService method from the body of an interface whose name is Service and inserts the execute method in its place regardless of what is in the interface body. The pattern has file scope.

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

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

identifier

Identifiers refer to names of variables, functions, methods, classes, packages, etc. They support ID matching, as well as insertion and removal operations. Any supported operator can be used in place of an identifier.

warning

It is important to remember that insertion and removal operators must always be used in pairs when dealing with an identifier. In other words, every time an identifier is removed, there must be a corresponding insertion. Failure to do so could result in a class, method, or variable being left unnamed or assigned multiple names.

The following example inserts a log command LOGGER.debug("Starting repo method"); at the beginning of all public methods that return void or Entity of a class with any name (identifier), in addition to inserting an attribute with the logger object and the necessary import. For this, we use three transformation patterns, the first with file scope and the other two with snippet scope.

<<<< 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

String literals can utilize the match, insertion, and removal operators. This allows you to apply any supported operators instead of a string literal.

warning

Insertion and removal operators must always occur in pairs within a string literal. This means that for every removal, there must be a corresponding insertion of a string literal.

The following example changes the log message for all calls to LOGGER.debug()` regardless of the string that is passed as a parameter. The pattern has snippet scope.

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

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

parameters and arguments

Parameters are used in method declarations and in the arguments of method calls. They support operators for insertion, removal, and ellipsis. This means that wherever parameters or arguments are applicable, you can use any of these supported operators.

warning

Parameters and arguments are constructed using commas. To effectively use operators in these constructs, it is advisable to refer to the section on Using operators in comma-separated constructs.

The following example changes the id parameter in the constructor of a class from int to long. It also makes the necessary changes to the id attribute itself and to the get and set methods. We use four transformation patterns, all with **snippet scope **, to do this.

<<<< 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;>-

The following example inserts a new parameter at the end of the parameter list of a method declaration and also inserts a new argument in all calls to that method. We use two transformation patterns with snippet scope to do this.

<<<< 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

Annotations support insertion, removal, and ellipsis operators. This means that any supported operator can be used anywhere annotations are valid.

The following example migrates the @Test annotations from JUnit 4, which accept parameters, to JUnit 5, which does not accept parameters. We use two transformation patterns, one with file scope and the other with snippet scope.

<<<< 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

A statement is a language command that produces a side effect and is not reduced to a value. This is the most common type of construct in Java. Here are some examples of statements:

  • Decision statements (if and switch)
  • Iteration statements (for, while and do-while)
  • try-catch-finally statement
  • synchronized statement
  • return, yield, break and continue statements
  • Variable assignment
  • Method call

Statements support insertion, removal, and ellipsis operators. This means that, in any place where the existence of statements is valid, it is possible to use any of the supported operators.

The following example inserts an if statement that checks whether the parameter of methods that begin with the name print and have a message parameter of type String is null and throws an exception if so. For this, we use a transformation pattern with snippet scope.

<<<< 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.");
}>+
<...>
}