Uživatelský modul plug-in může používat rozhraní API JDT k vytváření tříd a rozhraní, přidávání metod do stávajících typů a dále k pozměňování metod pro různé typy.
Nejjednodušším způsobem úpravy objektů Java je použití rozhraní API prvku Java. K práci se zdrojovým kódem prvku Java lze použít běžné přímé metody.
Nejsnadnějším způsobem vygenerování kompilační jednotky v rámci programu je použití IPackageFragment.createCompilationUnit. Zadejte název a obsah kompilační jednotky. Dojde k vytvoření nové kompilační jednotky v balíčku a je vrácena nová ICompilationUnit.
Kompilační jednotku lze vytvořit běžným způsobem vytvořením souboru s příponou názvu ".java" v příslušné složce odpovídající adresáři balíčku. Použití obecného rozhraní API prostředků otevírá zadní vrátka do sady nástrojů platformy Java, a proto se model Java aktualizuje až po potvrzení běžných listenerů změn prostředků a dále po aktualizování modelu Java novou kompilační jednotkou prostřednictvím listenerů JDT.
Nejjednodušší úpravy zdrojového textu v jazyce Java lze provádět prostřednictvím rozhraní API prvku Java.
Například můžete provádět dotazy na typ v kompilační jednotce. Jakmile získáte IType, můžete k přidání členů zdrojového kódu k typu použít protokoly createField, createInitializer, createMethod nebo createType. Tyto metody poskytují zdrojový kód a informaci o umístění člena.
Rozhraní ISourceManipulation definuje běžné manipulace se zdrojem prvků Java. Jde o metody přejmenování, přesouvání, kopírování a odstraňování člena typu.
Kód lze upravovat manipulací s kompilační jednotkou (v tomto případě dochází k úpravě základního IFile) nebo lze upravit kopii kompilační jednotky umístěnou v paměti, tzv. pracovní kopii.
Pracovní kopii získáte z kompilační jednotky s použitím metody getWorkingCopy. (Povšimněte si, že pro vytvoření pracovní kopie nemusí kompilační jednotka existovat v modelu Java.) Pokud někdo vytvoří takovouto pracovní kopii, nese odpovědnost za její likvidaci s použitím metody discardWorkingCopy.
Pracovní kopie upravují obsah vyrovnávací paměti v operační paměti. Metoda getWorkingCopy() sice vytváří výchozí vyrovnávací paměť, avšak klienti mohou disponovat vlastní implementací vyrovnávací paměti s použitím metody getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor). Klienti mohou s textem manipulovat přímo ve vyrovnávací paměti. Přitom však musí průběžně synchronizovat pracovní kopii s obsahem vyrovnávací paměti s použitím metody reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor).
Pracovní kopii lze ukládat na disk (nahrazovat původní kompilační jednotku) s použitím metody commitWorkingCopy.
Následující příklad úseku zdrojového kódu vytváří pracovní kopii pro kompilační jednotku s použitím uživatelského vlastníka pracovní kopie. Kód upraví obsah vyrovnávací paměti, zarovná změny, uloží tyto změny na disk a nakonec zlikviduje pracovní kopii.
// Získat původní kompilační jednotku ICompilationUnit originalUnit = ...; // Získat vlastníka pracovní kopie WorkingCopyOwner owner = ...; // Vytvořit pracovní kopii ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // Upravit obsah vyrovnávací paměti a srovnání IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // Potvrdit změny workingCopy.commitWorkingCopy(false, null); // Zlikvidovat pracovní kopii workingCopy.discardWorkingCopy();
Klienti mohou s použitím vlastníka pracovní kopie sdílet pracovní kopie. Pracovní kopii lze později načíst metodou findWorkingCopy. Sdílená pracovní kopie je z tohoto důvodu přiřazena původní kompilační jednotce a vlastníkovi pracovní kopie.
Následující příklad předvádí postup, ve kterém klient 1 vytvoří sdílenou pracovní kopii, klient 2 tuto pracovní kopii načte, klient 1 pracovní kopii zlikviduje a klient 2 se pokusí o její načtení a zjistí, že již neexistuje:
// Klient 1 & 2: Získat původní kompilační jednotku ICompilationUnit originalUnit = ...; // Klient 1 & 2: Získat vlastníka pracovní kopie WorkingCopyOwner owner = ...; // Klient 1: Vytvořit sdílenou pracovní kopii ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // Klient 2: Načíst sdílenou pracovní kopii ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // Je toto shodná pracovní kopie assert workingCopyForClient1 == workingCopyForClient2; // Klient 1: Zlikvidovat sdílenou pracovní kopii workingCopyForClient1.discardWorkingCopy(); // Klient 2: Pokus o načtení sdílené kopie a zjištění, že jde o nulový objekt workingCopyForClient2 = originalUnit.findWorkingCopy(owner); assert workingCopyForClient2 == null;
Objekt CompilationUnit lze vytvořit od začátku s použitím metod vytvoření vAST. Názvy těchto metod začínají řetězcem new.... Následuje příklad vytvoření třídy HelloWorld.
První úsek představuje vytvořený výstup:
package example;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello" + " world");
}
}
Následující úsek představuje kód, který vytváří výstup.
AST ast = new AST();
CompilationUnit unit = ast.newCompilationUnit();
PackageDeclaration packageDeclaration = ast.newPackageDeclaration();
packageDeclaration.setName(ast.newSimpleName("example"));
unit.setPackage(packageDeclaration);
ImportDeclaration importDeclaration = ast.newImportDeclaration();
QualifiedName name =
ast.newQualifiedName(
ast.newSimpleName("java"),
ast.newSimpleName("util"));
importDeclaration.setName(name);
importDeclaration.setOnDemand(true);
unit.imports().add(importDeclaration);
TypeDeclaration type = ast.newTypeDeclaration();
type.setInterface(false);
type.setModifiers(Modifier.PUBLIC);
type.setName(ast.newSimpleName("HelloWorld"));
MethodDeclaration methodDeclaration = ast.newMethodDeclaration();
methodDeclaration.setConstructor(false);
methodDeclaration.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
methodDeclaration.setName(ast.newSimpleName("main"));
methodDeclaration.setReturnType(ast.newPrimitiveType(PrimitiveType.VOID));
SingleVariableDeclaration variableDeclaration = ast.newSingleVariableDeclaration();
variableDeclaration.setModifiers(Modifier.NONE);
variableDeclaration.setType(ast.newArrayType(ast.newSimpleType(ast.newSimpleName("String"))));
variableDeclaration.setName(ast.newSimpleName("args"));
methodDeclaration.parameters().add(variableDeclaration);
org.eclipse.jdt.core.dom.Block block = ast.newBlock();
MethodInvocation methodInvocation = ast.newMethodInvocation();
name =
ast.newQualifiedName(
ast.newSimpleName("System"),
ast.newSimpleName("out"));
methodInvocation.setExpression(name);
methodInvocation.setName(ast.newSimpleName("println"));
InfixExpression infixExpression = ast.newInfixExpression();
infixExpression.setOperator(InfixExpression.Operator.PLUS);
StringLiteral literal = ast.newStringLiteral();
literal.setLiteralValue("Hello");
infixExpression.setLeftOperand(literal);
literal = ast.newStringLiteral();
literal.setLiteralValue(" world");
infixExpression.setRightOperand(literal);
methodInvocation.arguments().add(infixExpression);
ExpressionStatement expressionStatement = ast.newExpressionStatement(methodInvocation);
block.statements().add(expressionStatement);
methodDeclaration.setBody(block);
type.bodyDeclarations().add(methodDeclaration);
unit.types().add(type);
private int[] getOperatorPosition(Expression expression, char[] source) { if (expression instanceof InstanceofExpression) { IScanner scanner = ToolFactory.createScanner(false, false, false, false); scanner.setSource(source); int start = expression.getStartPosition(); int end = start + expression.getLength(); scanner.resetTo(start, end); int token; try { while ((token = scanner.getNextToken()) != ITerminalSymbols.TokenNameEOF) { switch(token) { case ITerminalSymbols.TokenNameinstanceof: return new int[] {scanner.getCurrentTokenStartPosition(), scanner.getCurrentTokenEndPosition()}; } } } catch (InvalidInputException e) { } } return null; }IScanner se používá k rozčlenění vstupního zdroje na tokeny. Každý z tokenů má specifickou hodnotu definovanou v rozhraní ITerminalSymbols. Iterace a získání pravého tokenu je velmi jednoduché. Rovněž doporučujeme použít objekt scanner, chcete-li nalézt polohu klíčového slova super v SuperMethodInvocation.
Určité úpravy zdrojového kódu nejsou podporovány rozhraním API prvku Java. Obecnějším způsobem úpravy zdrojového kódu (např. úprava zdrojového kódu stávajících prvků) je přepis rozhraní API DOM/AST v čistém zdrojovém kódu kompilační jednotky.
Pro přepis DOM/AST jsou k dispozici dvě sady rozhraní API: popisný přepis a upravující přepis.
Popisné rozhraní API neupravuje AST, ale používá rozhraní API ASTRewrite k vytvoření popisů úprav.
Přepisovač AST ukládá popisy úprav do uzlů a překládá tyto popisy do textových úprav, které lze uplatnit na původní zdrojový text.
// vytvoření dokumentu
ICompilationUnit cu = ... ; // obsahem je "public class X {\n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// vytvoření DOM/AST z ICompilationUnit
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// vytvoření ASTRewrite
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// popis změny
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// výpočet úprav textu
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// výpočet nového zdrojového kódu
edits.apply(document);
String newSource = document.get();
// aktualizace kompilační jednotky
cu.getBuffer().setContents(newSource);
Úprava rozhraní API umožňuje upravovat přímo abstraktní syntaktický strom:
// vytvoření dokumentu ICompilationUnit cu = ... ; // obsahem je "public class X {\n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // vytvoření DOM/AST z ICompilationUnit ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // spuštění záznamu úprav astRoot.recordModifications(); // úprava abstraktního syntaktického stromu TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // výpočet úprav textu TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // výpočet nového zdrojového kódu edits.apply(document); String newSource = document.get(); // aktualizace kompilační jednotky cu.getBuffer().setContents(newSource);
Pokud váš modul plug-in potřebuje znát provedené změny prvků Java, můžete registrovat listener Java IElementChangedListener s použitím JavaCore.
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
Můžete být konkrétnější a zadat typ událostí, o které se zajímáte, s použitím addElementChangedListener(IElementChangedListener, int).
Zajímá-li vás například pouze naslouchání událostem během provádění srovnání (reconcile):
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
JavaCore podporuje dva typy událostí:
Listenery změny prvku Java mají strukturu obdobnou listenerům změny prostředku (viz popis ve sledování změn prostředků). Následující úsek zdrojového textu implementuje prvek vytvářející zprávy o změně prvku Java, který zobrazuje rozdílová data prvku v textové konzole.
public class MyJavaElementChangeReporter implements IElementChangedListener { public void elementChanged(ElementChangedEvent event) { IJavaElementDelta delta= event.getDelta(); if (delta != null) { System.out.println("delta received: "); System.out.print(delta); } } }
IJavaElementDelta obsahuje prvek, který byl změněn, a příznaky popisující typ provedené změny. Po většinu času má strom rozdílových dat kořen na úrovni modelu Java. Klienti se v takovém případě musí přesouvat těmito rozdílovými daty s použitím getAffectedChildren za účelem vyhledání změněných projektů.
Následující metoda prochází rozdílovými daty a tiskne prvky, které byly přidány, odebrány nebo změněny:
void traverseAndPrint(IJavaElementDelta delta) { switch (delta.getKind()) { case IJavaElementDelta.ADDED: System.out.println(delta.getElement() + " byl přidán"); break; case IJavaElementDelta.REMOVED: System.out.println(delta.getElement() + " byl odebrán"); break; case IJavaElementDelta.CHANGED: System.out.println(delta.getElement() + " byl změněn"); if ((delta.getFlags() & IJavaElementDelta.F_CHILDREN) != 0) { System.out.println("Změna byla provedena v podřízeném prvku"); } if ((delta.getFlags() & IJavaElementDelta.F_CONTENT) != 0) { System.out.println("Změna byla provedena v jeho obsahu"); } /* Zkontrolovat lze rovněž další příznaky */ break; } IJavaElementDelta[] children = delta.getAffectedChildren(); for(int i=0; i < children.length; i++) { traverseAndPrint(children[i]); } }
Úkony určitých typů mohou spouštět potvrzování změn prvků Java. Uveďme několik příkladů:
Podobně jako IResourceDelta lze rozdílová data prvku Java dávkovat s použitím IWorkspaceRunnable. Rozdílová data vzniklá provedením určitého počtu úkonů modelu Java provedených v rámci IWorkspaceRunnable se spojí a jsou zobrazována společně.
JavaCore poskytuje metodu run pro dávkové zpracování změn prvků Java.
Následující úsek zdrojového kódu spouští dvě události změny prvku Java:
// Získat balíček IPackageFragment pkg = ...; // Vytvořit dvě kompilační jednotky ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
Následující úsek zdrojového kódu spouští jednu událost změny prvku Java:
// Získat balíček IPackageFragment pkg = ...; // Vytvořit dvě kompilační jednotky JavaCore.run( new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) throws CoreException { ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null); } }, null);