Obsah
1. Analýza a transformace kódu psaného v Javě s využitím knihovny Spoon
2. Lokální instalace a základní otestování knihovny Spoon
3. Grafické zobrazení abstraktního syntaktického stromu zdrojových kódů
4. Vytvoření a překlad velmi jednoduchého procesoru zdrojového kódu
5. Otestování procesoru zdrojového kódu
6. Procesor zjišťující pozice elementů ve zdrojovém kódu
7. Otestování druhého procesoru
8. Procesor zjišťující všechny deklarované třídy
9. Otestování třetího procesoru
10. Procesor detekující zápisy do proměnných
11. Otestování čtvrtého procesoru
12. Zdrojové kódy všech procesorů i testovaných příkladů
13. Použité třídy a rozhraní knihovny Spoon
1. Analýza a transformace kódu psaného v Javě s využitím knihovny Spoon
V seriálu o programovacím jazyku Java i o virtuálním stroji Javy jsme se již zabývali několika nástroji, které jsou používány pro analýzu programů a aplikací naprogramovaných v Javě, popř. pro nějakou transformaci kódů (například pro vložení volání loggeru na začátek vybraných metod atd.). Jednalo se ovšem prozatím převážně o knihovny a nástroje, které jsou založeny na zpracování a modifikaci bajtkódu. Zmínili jsme se například o knihovně ASM, dále pak o knihovně BCEL (Byte Code Engineering Library) a nesmíme zapomenout ani na knihovnu Javassist a utilitu Byteman. Nad těmito knihovnami jsou postaveny složitější a možná i známější nástroje typu FindBugs. Ovšem již při častějším používání FindBugs začne být patrné, že tento nástroj nemá (a ani nemůže mít) k dispozici všechny důležité informace o zpracovávaných kódech, protože jeho princip je založen na analýze bajtkódu, ke kterému se pouze přidávají další metainformace typu číslo řádku a jméno zdrojového souboru.
I některé další aplikace vyžadují poněkud odlišný přístup než „pouhou“ analýzu bajtkódu. Pro příklad samozřejmě nemusíme chodit daleko, protože takovou aplikací jsou všechna moderní integrovaná vývojová prostředí, která „rozumí“ zapisovanému zdrojovému kódu – nezpracovávají ho tedy jako pouhou sekvenci textových řádků, kde každý řádek obsahuje proměnlivý počet znaků, ale udržují si určitý sofistikovanější model zdrojového kódu, který umožňuje, aby IDE mohlo provádět kontextové doplňování identifikátorů (metod, atributů, tříd, …), refaktoring apod. Onen sofistikovaný model je ve většině případů založen na AST (Abstract Syntax Tree). A právě na programovou manipulaci s AST reprezentujícím zdrojové kódy Javy je zaměřena knihovna Spoon, jejíž základní vlastnosti si dnes přiblížíme. V nejjednodušším případě Spoon pracuje jako „kolona“ známá všem administrátorům unixových systémů:
zdrojový kód → AST → processor1 → processor2 → modifikovaný AST → výsledný zdrojový kód
Na vstupu i na výstupu tedy máme zdrojový kód, ovšem uprostřed „kolony“ se neprovádí nějaké textové substituce či aplikace textových maker (jak to známe z preprocesoru céčka), ale přímo manipulace s AST. V některých případech nás dokonce ani nezajímá výsledný zdrojový kód, ale pouze průběh analýzy; to využijeme například při hledání chyb, porovnávání dvou kódů (jeho sémantiky) atd. Ve skutečnosti ovšem může být manipulace s AST poměrně sofistikovaná, od přidání nových metod až po záměnu programových smyček, „expanzi“ anotace na skutečný programový kód (například při označení třídy jako jedináčka se může automaticky skrýt konstruktor) apod.
2. Lokální instalace a základní otestování knihovny Spoon
Knihovnu Spoon je možné nainstalovat a použít několika způsoby. Pokud při správě projektů používáte Maven, stačí do konfiguračního souboru projektu (.pom) přidat následující řádky odkazující se na Spoon verze 5.3.0:
<dependency> <groupId>fr.inria.gforge.spoon</groupId> <artifactId>spoon-core</artifactId> <version>5.3.0</version> </dependency>
Ve skutečnosti je však samozřejmě možné knihovnu Spoon nainstalovat a použít i bez Mavenu. Postačuje si pouze do pracovního adresáře stáhnout již připravený Java archiv (JAR) obsahující již přeloženou knihovnu a současně i všechny další potřebné balíčky. Takto vytvořený Java archiv vyžaduje ke své činnosti pouze JVM (navíc ještě budeme potřebovat alespoň překladač pro překlad tzv. procesorů). Pro naše účely se tedy celá instalace zmenší na jediný příkaz pro stažení Java archivu s knihovnou verze 5.3.0:
wget https://gforge.inria.fr/frs/download.php/file/36179/spoon-core-5.3.0-jar-with-dependencies.jar
Právě stažený Java archiv pro jistotu otestujeme. Po zadání tohoto příkazu:
java -jar spoon-core-5.3.0-jar-with-dependencies.jar
by se na standardní výstup měla vypsat nápověda:
Spoon version 5.3.0 Usage: java <launcher name> [option(s)] Options : [-h|--help] ... ... ...
Poznámka: všechny příklady byly otestovány na OpenJDK 1.7.0 a budou funkční i pro 1.8.0. Pokud stále ještě používáte Javu 5 či 6, bude pravděpodobně nutné si Spoon přeložit ze zdrojových kódů.
3. Grafické zobrazení abstraktního syntaktického stromu zdrojových kódů
Ještě než si popíšeme základní způsoby zpracování AST, můžeme se podívat na to, jak je vlastně takový AST v knihovně Spoon reprezentován. Vytvořme si pro tento účel tu nejjednodušší možnou třídu:
public class EmptyClass { }
Poznámka: třída je public z toho důvodu, aby bylo zřejmé, jak se jmenuje její soubor.
Tuto třídu nemusíme překládat, protože Spoon skutečně zpracovává zdrojový kód. Snadno se o tom můžeme přesvědčit po zadání následujícího příkazu v pracovním adresáři:
java -jar spoon-core-5.3.0-jar-with-dependencies.jar -g -i EmptyClass.java
V tomto příkazu specifikujeme jméno zpracovávané třídy parametrem -i a parametr -g zařídí, že Spoon zobrazí GUI okno se strukturou AST zobrazenou klasickým Swingovým „stromečkem“:
Obrázek 1: AST třídy EmptyClass. Každý uzel stromu má svůj typ (CtClass, CtBlock).
Zkusme ještě něco složitějšího; třídu Operators (k té se ještě dnes později vrátíme):
public class Operators { public static void main(String[] args) { int x, y, z; x = 10; y = 2 * x; z = y + x / 3 * (x + y); } }
java -jar spoon-core-5.3.0-jar-with-dependencies.jar -g -i Operators.java
Obrázek 2: AST třídy Operators.
Poznámka: uzly stromu jsou implicitně zabaleny, ovšem z kontextového menu (pravé tlačítko myši) lze zvolit příkaz Expand All na zobrazení celého stromu.
4. Vytvoření a překlad velmi jednoduchého procesoru zdrojového kódu
Okno se zobrazeným AST je vhodné jen pro první seznámení s tím, jakým způsobem je zdrojový kód v knihovně Spoon reprezentován, ovšem veškeré případné manipulace musí být provedeny programově. Pro tento účel se používají takzvané procesory, což jsou třídy rozšiřující abstraktní třídu spoon.processing.AbstractProcessor či některého jejího potomka. Konkrétní (neabstraktní) třídy odvozené od AbstractProcessor obsahují metodu process(), která je postupně volána pro každý element AST, jenž je zvoleného typu. Pro začátek se nebudeme zabývat typy elementů a ukážeme si implementaci procesoru, jehož metoda process() postupně získá a zpracuje všechny elementy (každý element je reprezentován instancí třídy implementující rozhraní CtElement, kde „CT“ značí „Compile Time“). Náš úplně první procesor je velmi jednoduchý:
import spoon.processing.AbstractProcessor; import spoon.reflect.declaration.CtElement; public class FirstProcessor extends AbstractProcessor { public void process(CtElement element) { System.out.println(element); } }
Pro překlad je pochopitelně nutné specifikovat, kde se nachází importovaná třída AbstractProcessor a rozhraní CtElement. Překlad se tedy provede následovně:
javac -cp spoon-core-5.3.0-jar-with-dependencies.jar FirstProcessor.java
5. Otestování procesoru zdrojového kódu
Otestujme si nyní právě přeložený procesor. Pro spuštění knihovny Spoon s procesorem se spouští metoda main třídy spoon.Launcher, samotný procesor se specifikuje přepínačem -p:
java -cp .:spoon-core-5.3.0-jar-with-dependencies.jar spoon.Launcher -p FirstProcessor -i zdrojový_kód
Zkusme si procesor otestovat na již představené třídě EmptyClass:
public class EmptyClass { }
java -cp .:spoon-core-5.3.0-jar-with-dependencies.jar spoon.Launcher -p FirstProcessor -i EmptyClass.java java.lang java.lang.Object java.lang java.lang.Object java.lang.Object#Object() super() { } public EmptyClass() { } public class EmptyClass {}
Metoda process() byla postupně volána pro všechny elementy AST a vypsala jejich textovou podobu CtElement.toString() na standardní výstup (ve skutečnosti není výsledek příliš čitelný, protože implementace metody CtElement.toString() jsou pojaty dosti minimalisticky).
Můžeme si ještě vyzkoušet nepatrně složitější příklad – třídu HelloWorld:
public class HelloWorld { private static String HELLO = "Hello world!"; public static void main(String[] args) { System.out.println(HELLO); } }
java -cp .:spoon-core-5.3.0-jar-with-dependencies.jar spoon.Launcher -p FirstProcessor -i HelloWorld.java java.lang java.lang.String java.lang java.lang.String "Hello world!" private static java.lang.String HELLO = "Hello world!"; java.lang java.lang.Object java.lang java.lang.Object java.lang.Object#Object() super() { } public HelloWorld() { } void java.lang java.lang.String java.lang.String[] java.lang.String[] args java.lang java.lang.System java.lang.System java.lang java.lang.System java.io java.io.PrintStream java.lang.System.out java.lang.System.out java.io java.io.PrintStream void java.lang java.lang.String java.io.PrintStream#println(java.lang.String) HelloWorld HelloWorld HelloWorld java.lang java.lang.String HelloWorld.HELLO HelloWorld.HELLO java.lang.System.out.println(HelloWorld.HELLO) { java.lang.System.out.println(HelloWorld.HELLO); } public static void main(java.lang.String[] args) { java.lang.System.out.println(HelloWorld.HELLO); } public class HelloWorld { private static java.lang.String HELLO = "Hello world!"; public static void main(java.lang.String[] args) { java.lang.System.out.println(HelloWorld.HELLO); } } 0 packages
Poznámka: povšimněte si, že při spuštění procesoru vznikl adresář spooned, do kterého jsou uloženy transformované třídy. Ve skutečnosti se vlastně žádná skutečná transformace (modifikace) neprováděla, takže třídy jsou vlastně sémanticky totožné.
6. Procesor zjišťující pozice elementů ve zdrojovém kódu
První implementace procesoru byla velmi primitivní (jediný programový řádek v metodě process), takže se ani není čemu divit, že generovaný výstup je prakticky nečitelný. Zkusme si nyní vytvořit nepatrně složitější procesor, který u každého elementu vypíše, na kterém místě ve zdrojovém kódu se tento element nachází, dále pak typ elementu (resp. přesněji řečeno jméno třídy implementující rozhraní CtElement) a nakonec lepší textovou reprezentaci elementu zjišťovanou metodou CtElement.getShortRepresentation. Vylepšený procesor vypadá následovně:
import spoon.processing.AbstractProcessor; import spoon.reflect.declaration.CtElement; import spoon.reflect.cu.SourcePosition; public class ListSourcePositionProcessor extends AbstractProcessor { public void process(CtElement element) { SourcePosition sp = element.getPosition(); String position = sp == null ? "unknown" : sp.toString(); System.out.println(position + "\t" + element.getClass().getName() + "\t" + element.getShortRepresentation()); } }
Povšimněte si, že nejprve zjišťujeme pozici elementu ve zpracovávaném zdrojovém kódu, která však nemusí být u všech elementů známá. Pokud nelze pozici elementu určit, vrátí se null, který je nutné korektně zpracovat.
Překlad vylepšeného procesoru:
javac -cp spoon-core-5.3.0-jar-with-dependencies.jar ListSourcePositionProcessor.java
7. Otestování druhého procesoru
Druhý procesor zjišťující pozice elementů a jejich typ, si otestujeme jak na třídě EmptyClass, tak i na třídě HelloWorld:
java -cp .:spoon-core-5.3.0-jar-with-dependencies.jar spoon.Launcher -p ListSourcePositionProcessor -i EmptyClass.java unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.lang unknown spoon.support.reflect.reference.CtTypeReferenceImpl java.lang.Object unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.lang unknown spoon.support.reflect.reference.CtTypeReferenceImpl java.lang.Object unknown spoon.support.reflect.reference.CtExecutableReferenceImpl java.lang.Object#Object() (/home/tester/spoon/EmptyClass.java:1) spoon.support.reflect.code.CtInvocationImpl (java.lang.Object#Object()()) (/home/tester/spoon/EmptyClass.java:1) spoon.support.reflect.code.CtBlockImpl { (java.lang.Object#Object()()); } (/home/tester/spoon/EmptyClass.java:1) spoon.support.reflect.declaration.CtConstructorImpl EmptyClass() (/home/tester/spoon/EmptyClass.java:1) spoon.support.reflect.declaration.CtClassImpl class EmptyClass unknown spoon.reflect.CtModelImpl$CtRootPackage
Z výpisu, který je vhodné porovnat s prvním obrázkem ve třetí kapitole, je patrné, proč se u některých elementů nedá určit jejich vazba na konkrétní místo ve zdrojovém kódu. Taktéž si při porovnání s obrázkem můžeme uvědomit, že se metoda process volá pro všechny uzly AST, ovšem prohledávání je prováděno do hloubky, takže se kořenový uzel zpracuje jako poslední, což má svůj význam například při manipulaci s bloky či s výrazy.
java -cp .:spoon-core-5.3.0-jar-with-dependencies.jar spoon.Launcher -p ListSourcePositionProcessor -i HelloWorld.java unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.lang (/home/tester/spoon/HelloWorld.java:2) spoon.support.reflect.reference.CtTypeReferenceImpl java.lang.String unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.lang unknown spoon.support.reflect.reference.CtTypeReferenceImpl java.lang.String (/home/tester/spoon/HelloWorld.java:2) spoon.support.reflect.code.CtLiteralImpl "Hello world!" (/home/tester/spoon/HelloWorld.java:2) spoon.support.reflect.declaration.CtFieldImpl java.lang.String HELLO unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.lang unknown spoon.support.reflect.reference.CtTypeReferenceImpl java.lang.Object unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.lang unknown spoon.support.reflect.reference.CtTypeReferenceImpl java.lang.Object unknown spoon.support.reflect.reference.CtExecutableReferenceImpl java.lang.Object#Object() (/home/tester/spoon/HelloWorld.java:1) spoon.support.reflect.code.CtInvocationImpl (java.lang.Object#Object()()) (/home/tester/spoon/HelloWorld.java:1) spoon.support.reflect.code.CtBlockImpl { (java.lang.Object#Object()()); } (/home/tester/spoon/HelloWorld.java:1) spoon.support.reflect.declaration.CtConstructorImpl HelloWorld() (/home/tester/spoon/HelloWorld.java:4) spoon.support.reflect.reference.CtTypeReferenceImpl void unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.lang unknown spoon.support.reflect.reference.CtTypeReferenceImpl java.lang.String (/home/tester/spoon/HelloWorld.java:4) spoon.support.reflect.reference.CtArrayTypeReferenceImpl java.lang.String[] (/home/tester/spoon/HelloWorld.java:4) spoon.support.reflect.declaration.CtParameterImpl args unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.lang unknown spoon.support.reflect.reference.CtTypeReferenceImpl java.lang.System (/home/tester/spoon/HelloWorld.java:5) spoon.support.reflect.code.CtTypeAccessImpl java.lang.System unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.lang unknown spoon.support.reflect.reference.CtTypeReferenceImpl java.lang.System unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.io unknown spoon.support.reflect.reference.CtTypeReferenceImpl java.io.PrintStream (/home/tester/spoon/HelloWorld.java:5) spoon.support.reflect.reference.CtFieldReferenceImpl java.io.PrintStream java.lang.System#out (/home/tester/spoon/HelloWorld.java:5) spoon.support.reflect.code.CtFieldReadImpl java.io.PrintStream java.lang.System#out unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.io unknown spoon.support.reflect.reference.CtTypeReferenceImpl java.io.PrintStream unknown spoon.support.reflect.reference.CtTypeReferenceImpl void unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.lang unknown spoon.support.reflect.reference.CtTypeReferenceImpl java.lang.String unknown spoon.support.reflect.reference.CtExecutableReferenceImpl java.io.PrintStream#println(java.lang.String) unknown spoon.support.reflect.reference.CtPackageReferenceImpl unknown spoon.support.reflect.reference.CtTypeReferenceImpl HelloWorld unknown spoon.support.reflect.code.CtTypeAccessImpl HelloWorld unknown spoon.support.reflect.reference.CtPackageReferenceImpl unknown spoon.support.reflect.reference.CtTypeReferenceImpl HelloWorld unknown spoon.support.reflect.reference.CtPackageReferenceImpl java.lang unknown spoon.support.reflect.reference.CtTypeReferenceImpl java.lang.String unknown spoon.support.reflect.reference.CtFieldReferenceImpl java.lang.String HelloWorld#HELLO (/home/tester/spoon/HelloWorld.java:5) spoon.support.reflect.code.CtFieldReadImpl java.lang.String HelloWorld#HELLO (/home/tester/spoon/HelloWorld.java:5) spoon.support.reflect.code.CtInvocationImpl (java.io.PrintStream#println(java.lang.String)(java.lang.String HelloWorld#HELLO)) (/home/tester/spoon/HelloWorld.java:4) spoon.support.reflect.code.CtBlockImpl { (java.io.PrintStream#println(java.lang.String)(java.lang.String HelloWorld#HELLO)); } (/home/tester/spoon/HelloWorld.java:4) spoon.support.reflect.declaration.CtMethodImpl void main(java.lang.String[]) (/home/tester/spoon/HelloWorld.java:1) spoon.support.reflect.declaration.CtClassImpl class HelloWorld unknown spoon.reflect.CtModelImpl$CtRootPackage
8. Procesor zjišťující všechny deklarované třídy
Procesory ve skutečnosti nemusí zpracovávat všechny typy elementů, ale je možné provádět různé formy filtrace. Nejjednodušší filtrace je zajištěna explicitním určením typů elementů – namísto obecného rozhraní CtElement lze zvolit konkrétní typ elementu či množinu elementů z následujícího seznamu rozhraní: CtAbstractInvocation, CtAnnotation, CtAnnotationFieldAccess, CtAnnotationMethod, CtAnnotationType, CtAnonymousExecutable, CtArrayAccess, CtArrayRead, CtArrayTypeReference, CtArrayWrite, CtAssert, CtAssignment, CtBinaryOperator, CtBlock, CtBreak, CtCase, CtCatch, CtCatchVariable, CtCatchVariableReference, CtCFlowBreak, CtClass, CtCodeElement, CtCodeSnippetExpression, CtCodeSnippetStatement, CtComment, CtConditional, CtConstructor, CtConstructorCall, CtContinue, CtDo, CtEnum, CtEnumValue, CtExecutable, CtExecutableReference, CtExecutableReferenceExpression, CtExpression, CtField, CtFieldAccess, CtFieldRead, CtFieldReference, CtFieldWrite, CtFor, CtForEach, CtFormalTypeDeclarer, CtIf, CtInterface, CtIntersectionTypeReference, CtInvocation, CtLambda, CtLiteral, CtLocalVariable, CtLocalVariableReference, CtLoop, CtMethod, CtModifiable, CtMultiTypedElement, CtNamedElement, CtNewArray, CtNewClass, CtOperatorAssignment, CtPackage, CtPackageReference, CtParameter, CtParameterReference, CtReference, CtReturn, CtStatement, CtStatementList, CtSuperAccess, CtSwitch, CtSynchronized, CtTargetedExpression, CtThisAccess, CtThrow, CtTry, CtTryWithResource, CtType, CtTypeAccess, CtTypedElement, CtTypeMember, CtTypeParameter, CtTypeParameterReference, CtTypeReference, CtUnaryOperator, CtUnboundVariableReference, CtVariable, CtVariableAccess, CtVariableRead, CtVariableReference, CtVariableWrite, CtWhile, CtWildcardReference.
Rozhraní tvoří hierarchii, například CtElement → CtLoop → CtDo, CtFor, CtForEach, CtWhile atd.
Podívejme se nyní na praktický příklad. Bude se jednat o procesor, který vypíše všechny deklarované třídy nalezené ve zpracovávaném zdrojovém kódu. V procesoru jsou provedeny dvě změny. Nejprve uvedeme typ zpracovávaných elementů v deklaraci třídy (namísto extends AbstractProcessor zapíšeme konkrétně extends AbstractProcessor<CtClass> a metoda process je upravena takovým způsobem, že zpracovává elementy typu CtClass a nikoli CtElement, takže se vyhneme zbytečnému přetypování:
import spoon.processing.AbstractProcessor; import spoon.reflect.declaration.CtClass; import spoon.reflect.cu.SourcePosition; public class ListClassesProcessor extends AbstractProcessor<CtClass> { public void process(CtClass element) { SourcePosition sp = element.getPosition(); String position = sp == null ? "unknown" : sp.toString(); System.out.println(position + "\t" + element.getSimpleName()); } }
Překlad procesoru provedeme nám již známým příkazem:
javac -cp spoon-core-5.3.0-jar-with-dependencies.jar ListClassesProcessor.java
9. Otestování třetího procesoru
Nejprve nový procesor otestujeme na naší známé třídě EmptyClass:
java -cp .:spoon-core-5.3.0-jar-with-dependencies.jar spoon.Launcher -p ListClassesProcessor -i EmptyClass.java (/home/tester/spoon/HelloWorld.java:1) HelloWorld
Nic překvapivého se nevypsalo, ale můžeme zde vidět, že správnou filtrací lze dosáhnout mnohem uspokojivějších výsledků.
Zkusme tedy něco složitějšího, a to konkrétně následující zdrojový kód s několika top-level třídami a vnořenými třídami:
public class ManyClasses { class InnerClass { } class OtherInnerClass { class NestedClass { } } } class OtherClass { } class YetAnotherClass { }
Spusťme náš nový procesor:
java -cp .:spoon-core-5.3.0-jar-with-dependencies.jar spoon.Launcher -p ListClassesProcessor -i ManyClasses.java (/home/tester/spoon/ManyClasses.java:2) InnerClass (/home/tester/spoon/ManyClasses.java:5) NestedClass (/home/tester/spoon/ManyClasses.java:4) OtherInnerClass (/home/tester/spoon/ManyClasses.java:1) ManyClasses (/home/tester/spoon/ManyClasses.java:10) OtherClass (/home/tester/spoon/ManyClasses.java:13) YetAnotherClass
Povšimněte si způsobu výpisu – první třída obsahující dvě vnitřní třídy je vypsána až na čtvrtém místě, tj. ve chvíli, kdy má procesor šanci správně zpracovat všechny vnitřní třídy.
10. Procesor detekující zápisy do proměnných
Všechny tři předchozí procesory vlastně ukazovaly pouze nepatrnou část možností knihovny Spoon. Podívejme se na složitější a možná již praktičtější příklad. Budeme v něm hledat všechna místa v programovém kódu, v nichž se zapisuje do proměnných. Pro každou operaci zápisu, kterou nalezneme, vypíšeme jak pozici ve zdrojovém kódu, tak i jméno proměnné. Náš procesor nyní bude filtrovat jen elementy typu CtVariableWrite a nikoli všechny elementy CtElement. Dále pro každý nalezený element v AST přečteme, o jakou se jedná proměnnou. Objekt představující referenci na proměnnou v AST má typ CtVariableReference, nás ovšem v tuto chvíli zajímá pouze jeho metoda getSimpleName(). Výsledná implementace procesoru může vypadat takto:
import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtVariableWrite; import spoon.reflect.reference.CtVariableReference; import spoon.reflect.cu.SourcePosition; public class VariableWriteProcessor extends AbstractProcessor<CtVariableWrite> { public void process(CtVariableWrite element) { SourcePosition sp = element.getPosition(); CtVariableReference variable = element.getVariable(); String variableName = variable.getSimpleName(); String position = sp == null ? "unknown" : sp.toString(); System.out.println("write into variable '" + variableName + "' at " + position); } }
Příkaz pro překlad procesoru již známe:
javac -cp spoon-core-5.3.0-jar-with-dependencies.jar VariableWriteProcessor.java
11. Otestování čtvrtého procesoru
Pro otestování čtvrtého procesoru použijeme třídu Operators, s níž jsme se již seznámili ve třetí kapitole (v ní je zobrazena část AST této třídy):
public class Operators { public static void main(String[] args) { int x, y, z; x = 10; y = 2 * x; z = y + x / 3 * (x + y); } }
Zkusme nyní nalézt všechny zápisy do proměnných ve třídě Operators:
java -cp .:spoon-core-5.3.0-jar-with-dependencies.jar spoon.Launcher -p VariableWriteProcessor -i Operators.java
Zde je výsledek, který je již na první pohled korektní:
write into variable 'x' at (/home/tester/spoon/Operators.java:4) write into variable 'y' at (/home/tester/spoon/Operators.java:5) write into variable 'z' at (/home/tester/spoon/Operators.java:6)
Poznámka: tento procesor nedokáže (a ani nemá dokázat) zobrazit deklaraci proměnné s přiřazením.
12. Zdrojové kódy všech procesorů i testovaných příkladů
13. Použité třídy a rozhraní knihovny Spoon
- spoon.processing.AbstractProcessor
http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/processing/AbstractProcessor.html - spoon.reflect.declaration.CtElement
http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/reflect/declaration/CtElement.html - spoon.reflect.declaration.CtClass
http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/reflect/declaration/CtClass.html - spoon.reflect.code.CtVariableWrite
http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/reflect/code/CtVariableWrite.html - spoon.reflect.cu.SourcePosition
http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/reflect/cu/SourcePosition.html - spoon.reflect.reference.CtVariableReference
http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/reflect/reference/CtVariableReference.html
14. Obsah druhé části článku
Dnes jsme si ukázali pouze naprosté základy nabízené knihovnou Spoon. Příště se seznámíme se složitějšími procesory a taktéž s takzvanými šablonami, které lze využít pro automatizaci různých transformací zdrojového kódu.
15. Odkazy na Internetu
- Spoon – Source Code Analysis and Transformation for Jav
http://spoon.gforge.inria.fr/ - Project Filelist for Spoon
https://gforge.inria.fr/frs/?group_id=73 - Spoon na GitHubu
https://github.com/INRIA/spoon - Instance Jenkinsu, v níž se překládá Spoon
https://ci.inria.fr/sos/ - JavaParser: Java Parser and Abstract Syntax Tree
http://javaparser.org/ - Javaparser na GitHubu
https://github.com/javaparser/javaparser - FindBugs Fact Sheet
http://findbugs.sourceforge.net/factSheet.html - Open Source ByteCode Libraries in Java
http://java-source.net/open-source/bytecode-libraries - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - BCEL Home page
http://commons.apache.org/bcel/ - Byte Code Engineering Library (před verzí 5.0)
http://bcel.sourceforge.net/ - Byte Code Engineering Library (verze >= 5.0)
http://commons.apache.org/proper/commons-bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - Javassist
http://www.jboss.org/javassist/ - Byteman
http://www.jboss.org/byteman - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - jclasslib bytecode viewer
http://www.ej-technologies.com/products/jclasslib/overview.html - Abstract syntax tree
https://en.wikipedia.org/wiki/Abstract_syntax_tree