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.
- Original Code
- Transformation Pattern
- Transformed Code
import java.util.List;
import java.util.LinkedList;
<<<< pattern-name: change-import-arraylist-to-linked-list; pattern-scope: file-scope; >>>>
<...>
-<import java.util.LinkedList;>-
+<import java.util.ArrayList;>+
<...>
import java.util.List;
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.
- Original Code
- Transformation Pattern
- Transformed Code
public class MyClass {
private String stringField;
private int intField;
public MyClass(String stringField, int intField) {
this.stringField = stringField;
this.intField = intField;
}
public void setStringField(String stringField) {
this.stringField = stringField;
}
public String getStringField() {
return this.stringField;
}
public void setIntField(int intField) {
this.intField = intField;
}
public int getIntField() {
return this.intField;
}
}
<<<< pattern-name: modify-java-code; pattern-scope: file-scope; >>>>
<...>
+<public enum MyEnum {
ITEM_1,
ITEM_2,
ITEM_3,
}>+
public class MyClass {
<...>
}
<...>
public enum MyEnum {
ITEM_1,
ITEM_2,
ITEM_3,
}
public class MyClass {
private String stringField;
private int intField;
public MyClass(String stringField, int intField) {
this.stringField = stringField;
this.intField = intField;
}
public void setStringField(String stringField) {
this.stringField = stringField;
}
public String getStringField() {
return this.stringField;
}
public void setIntField(int intField) {
this.intField = intField;
}
public int getIntField() {
return this.intField;
}
}
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.
- Original Code
- Transformation Pattern
- Transformed Code
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
<<<< 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;
}>+
}
<...>
public class Person {
private int age;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
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.
- Original Code
- Transformation Pattern
- Transformed Code
public interface Service {
public void executeService();
}
<<<< pattern-name: modify-person-class; pattern-scope: file-scope; >>>>
<...>
public interface Service {
<...>
-<public void executeService();>-
+<public void execute();>+
<...>
}
<...>
public interface Service {
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.
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.
- Original Code
- Transformation Pattern
- Transformed Code
public class MyRepo {
public void createEntity(Entity obj){
// creates the entity
}
public Entity retrieveEntity(long id){
// retrieves the entity by id
}
public void updateEntity(Entity obj){
// updates the entity
}
public void deleteEntity(Entity obj){
// deletes the entity
}
}
<<<< 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");>+
<...>
}
import org.apache.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class MyRepo {
private static final Logger LOGGER = LogManager.getLogger(this.getClass());
public void createEntity(Entity obj){
LOGGER.debug("Starting repo method");
// creates the entity
}
public Entity retrieveEntity(long id){
LOGGER.debug("Starting repo method");
// retrieves the entity by id
}
public void updateEntity(Entity obj){
LOGGER.debug("Starting repo method");
// updates the entity
}
public void deleteEntity(Entity obj){
LOGGER.debug("Starting repo method");
// deletes the entity
}
}
stringLiteral
String literals can utilize the match, insertion, and removal operators. This allows you to apply any supported operators instead of a string literal.
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.
- Original Code
- Transformation Pattern
- Transformed Code
import org.apache.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class MyRepo {
private static final Logger LOGGER = LogManager.getLogger(this.getClass());
public void createEntity(Entity obj){
LOGGER.debug("Starting repo method");
// creates the entity
}
public Entity retrieveEntity(long id){
LOGGER.debug("Starting repo method");
// retrieves the entity by id
}
public void updateEntity(Entity obj){
LOGGER.debug("Starting repo method");
// updates the entity
}
public void deleteEntity(Entity obj){
LOGGER.debug("Starting repo method");
// deletes the entity
}
}
<<<< pattern-name: change-log-message; pattern-scope: snippet-scope; >>>>
LOGGER.debug(-<*<".*">*>- +<"My new debug message">+); // matches with any string
import org.apache.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class MyRepo {
private static final Logger LOGGER = LogManager.getLogger(this.getClass());
public void createEntity(Entity obj){
LOGGER.debug("My new debug message");
// creates the entity
}
public Entity retrieveEntity(long id){
LOGGER.debug("My new debug message");
// retrieves the entity by id
}
public void updateEntity(Entity obj){
LOGGER.debug("My new debug message");
// updates the entity
}
public void deleteEntity(Entity obj){
LOGGER.debug("My new debug message");
// deletes the entity
}
}
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.
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.
- Original Code
- Transformation Pattern
- Transformed Code
public class MyEntity {
private Integer id;
// other fields
public MyEntity(int id /* other parameters */){
// constructor implementation
}
public void setId(int id){
this.id = id;
}
public int getId(){
return this.id;
}
// other getters and setters
}
<<<< 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;>-
public class MyEntity {
private Long id;
// other fields
public MyEntity(long id /* other parameters */){
// constructor implementation
}
public void setId(long id){
this.id = id;
}
public long getId(){
return this.id;
}
// other getters and setters
}
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.
- Original Code
- Transformation Pattern
- Transformed Code
public class MyClass {
public static void myMethod(String param){
// do something
}
public static void main(String[] args) {
myMethod("message");
}
}
<<<< 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>+);
public class MyClass {
public static void myMethod(String param, int param2){
// do something
}
public static void main(String[] args) {
myMethod("message", 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.
- Original Code
- Transformation Pattern
- Transformed Code
import org.junit.Test;
public class MyTestClass {
@Test(expected = ArithmeticException.class)
public void testDivisionByZero() {
int result = 10 / 0; // This will throw an ArithmeticException
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidArgument() {
throw new IllegalArgumentException("Invalid argument");
}
@Test(expected = NullPointerException.class)
public void testNullPointer() {
String str = null;
str.length(); // This will throw a NullPointerException
}
@Test(expected = ArrayIndexOutOfBoundsException.class)
public void testArrayIndexOutOfBounds() {
int[] arr = new int[2];
int result = arr[5]; // This will throw an ArrayIndexOutOfBoundsException
}
@Test(expected = NumberFormatException.class)
public void testNumberFormatException() {
Integer.parseInt("abc"); // This will throw a NumberFormatException
}
}
<<<< 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>+
import org.junit.jupiter.api.Test;
public class MyTestClass {
@Test
public void testDivisionByZero() {
int result = 10 / 0; // This will throw an ArithmeticException
}
@Test
public void testInvalidArgument() {
throw new IllegalArgumentException("Invalid argument");
}
@Test
public void testNullPointer() {
String str = null;
str.length(); // This will throw a NullPointerException
}
@Test
public void testArrayIndexOutOfBounds() {
int[] arr = new int[2];
int result = arr[5]; // This will throw an ArrayIndexOutOfBoundsException
}
@Test
public void testNumberFormatException() {
Integer.parseInt("abc"); // This will throw a NumberFormatException
}
}
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
andswitch
) - Iteration statements (
for
,while
anddo-while
) try-catch-finally
statementsynchronized
statementreturn
,yield
,break
andcontinue
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.
- Original Code
- Transformation Pattern
- Transformed Code
public class ConsolePrinter {
public void printInfo(String message){
System.out.print("[INFO] " + message)
}
public void printWarning(String message){
System.out.print("[WARNING] " + message)
}
public void printError(String message){
System.out.print("[ERROR] " + message)
}
}
<<<< 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.");
}>+
<...>
}
public class ConsolePrinter {
public void printInfo(String message){
if(message == null){
throw new IllegalArgumentException("The message cannot be null.");
}
System.out.print("[INFO] " + message)
}
public void printWarning(String message){
if(message == null){
throw new IllegalArgumentException("The message cannot be null.");
}
System.out.print("[WARNING] " + message)
}
public void printError(String message){
if(message == null){
throw new IllegalArgumentException("The message cannot be null.");
}
System.out.print("[ERROR] " + message)
}
}