V tomto článku se podíváme na to, jak je
implementován modul RefactoringNG, a vysvětlíme si
syntaxi jeho refaktorizačních pravidel.

Překladač javac

Nejprve něco o tom, jak funguje překladač javac. Překladač zpracovává zdrojový kód v
několika krocích. Nejprve se text převede na
posloupnost lexikálních symbolů. Např. class
Main { }
se převede na KEYWORD_CLASS,
IDENTIFIER, LEFT_BRACE, RIGHT_BRACE
. Této
fázi se říká lexikální analýza. V další fázi, kterou
nazýváme syntaktická analýza, se kontroluje, zda jsou
lexikální symboly správně za sebou. Lexikální analýza
tedy provádí předzpracování zdrojového kódu pro
syntaktickou analýzu. Během syntaktické analýzy dojde
k postavení stromu, ze kterého se bude později
generovat výstupní kód. Tento strom se nazývá abstraktní syntaktický strom (zkráceně AST z
anglického abstract syntax tree). Na syntaktickou
analýzu navazuje analýza sémantická, která kontroluje
např. to, zda byla použitá proměnná deklarována.
Výstupem sémantické analýzy je strom, ve kterém má
každý identifikátor vazbu na svoji deklaraci. Tj.
např. pro každou proměnnou známe její typ. Překladač
javac zpřístupňuje AST přes Compiler Tree API. Protože v AST jsou všechny
podstatné informace ze zdrojového kódu, lze provést i
opačnou transformaci a AST převést na zdrojový kód.
Toho využívají NetBeans, které implementují refactoring javovského kódu na úrovni AST.
Když v NetBeans např. přejmenujete metodu, provede se
přejmenování v AST a pak se z AST vygeneruje zdrojový
kód, ve kterém je nové jméno. Navíc NetBeans nabízejí
API (balík org.netbeans.api.java.source), přes které lze
AST měnit.

RefactoringNG

RefactoringNG používá toto API pro refactoring javovského kódu. Zjednodušeně
řečeno, RefactoringNG umí pouze přepsat jeden AST na
jiný. Tomu odpovídá zápis refaktorizačních pravidel.
Každé pravidlo má tvar

Pattern -> Rewrite

kde Pattern je původní AST a Rewrite je nový AST. Pattern
i Rewrite mají stejnou strukturu:

Tree attributes content

kde attributes jsou uzavřeny do
hranatých závorek a oddělují se čárkou. Atributy jsou
pojmenovány stejně jako vlastnosti AST v javac. Např. atribut kind v
zápise

Literal [kind: NULL_LITERAL]

říká, že jde o literál null. Pokud
nějaký atribut není uveden, může mít libovolnou
hodnotu. Např.

Identifier

představuje libovolný identifikátor a

Literal [kind: INT_LITERAL]

je libovolný literál typu int.
Naproti tomu

Identifier [name: "answer"]

je identifikátor answer a

Literal [kind: INT_LITERAL, value: 42]

je literál 42. V části Rewrite musí být AST vždy popsán tak, aby
jej bylo možné vytvořit. Např. každý Identifier musí obsahovat atribut name.

V části content uzlu uvádíme
potomky tohoto uzlu. Seznam potomků je uzavřen do
složených závorek a položky seznamu jsou odděleny
čárkou. Např.

Binary [kind: PLUS] {

Literal [kind: INT_LITERAL],

Literal [kind: INT_LITERAL]

}

je sčítání dvou hodnot typu int.
Potomci daného uzlu musí být správného typu a musí
být uvedeni všichni, pokud má uzel nějaký obsah.
Např. Binary musí mít vždy dva potomky
(operandy), pokud má stanoven obsah, a oba musí být
typu Expression nebo libovolný podtyp.
Pokud Binary nemá žádný obsah,
operandy mohou mít libovolnou hodnotu. Např.

Binary [kind: MINUS]

je libovolné odčítání. To lze zapsat i takto:

Binary [kind: MINUS] {

Expression,

Expression

}

Expression zde znamená libovolný
výraz, protože nemá stanoven žádný atribut. Je-li
očekáván uzel nějakého typu, lze použít také
libovolný podtyp. Např. operandem uzlu Binary může být libovolný podtyp Expression.

Binary [kind: MULTIPLY] {

Identifier,

Literal [kind: INT_LITERAL, value: 0]

}

V RefactoringNG se používá stejná hierarchie typů
jako v javac.

Některé atributy mohou mít více hodnot. Hodnoty se
pak oddělují svislítkem. Např.

Binary [kind: PLUS | MINUS]

je buď sčítání nebo odčítání.

Každý uzel v části Pattern může mít
atribut id. Hodnota tohoto atributu
musí být unikátní v celém pravidle a slouží k
odkazování na daný uzel z části Rewrite. Např.

Assignment {

Identifier [id: p],

Literal [kind: NULL_LITERAL]

} ->

Assignment {

Identifier [ref: p],

Literal [kind: INT_LITERAL, value: 0]

}

přepíše p = null na p =
0
, kde p zastupuje libovolný
identifikátor.

Odkazy na atributy se zapisují pomocí #. Např. b#kind odkazuje na
atribut kind uzlu b.
Odkaz na atribut může být použit v části Rewrite jako hodnota atributu. Např.
následující pravidlo prohodí argumenty operace dělení
nebo zbytek po dělení:

Binary [id: b, kind: DIVIDE | REMAINDER] {

Identifier [id: x],

Identifier [id: y]

} ->

Binary [kind: b#kind] {

Identifier [ref: y],

Identifier [ref: x]

}

Tj. x/y se přepíše na y/x a x%y se přepíše y%x, kde x a y
zastupují libovolné identifikátory.

Hodnota null znamená, že daný uzel
v AST chybí. Např. pravidlo

Variable [id: v] {

Modifiers [id: m],

PrimitiveType [primitiveTypeKind: INT],

null

} ->

Variable [name: v#name] {

Modifiers [ref: m],

PrimitiveType [primitiveTypeKind: INT],

Literal [kind: INT_LITERAL, value: 42]

}

doplní k deklaraci proměnné typu int inicializaci na hodnotu 42. Tj.
např.

int x;

se přepíše na

int x = 42;

Na závěr si ukážeme, jak RefactoringNG vypadá.
Nejprve editor pravidel s kontrolou syntaxe a
kontextovou nápovědou:

refactoringng1

Práci s RefactoringNG si ukážeme na tomto kódu:

refactoringng2

Refactoring zahajíme výběrem položky z
menu:

refactoringng3

RefactoringNG aplikuje vybraná pravidla a zobrazí
navrhované změny. Pro provedení změn je třeba je
potvrdit:

refactoringng4

RefactoringNG umí použít refaktorizační pravidlo
také pro řádkový hint:

refactoringng5

A to je pro dnešek všechno. Pokud se chcete o
RefactoringNG dozvědět více, podívejte se na Wiki projektu.