U kunt de API van JDT gebruiken voor plugin om klassen of interfaces te maken, methoden aan bestaande typen toe te voegen, of methoden voor typen te wijzigen.
U kunt Java-objecten het gemakkelijkst wijzigen met de API voor Java-elementen. Daarnaast zijn er enkele algemene methoden om de 'ruwe' broncode van een Java-element te bewerken.
U kunt eenvoudig programmatisch een compilatie-eenheid genereren met IPackageFragment.createCompilationUnit. U geeft de naam en inhoud van de compilatie-eenheid op. De compilatie-eenheid wordt gemaakt in het pakket en de nieuwe ICompilationUnit wordt geretourneerd.
Een algemene methode om een compilatie-eenheid te maken is een bestandsresource met de extensie ".java" te maken in de juiste map van de bijbehorende pakketdirectory. De generieke resource-API dient slechts te worden gebruikt als alternatief voor de Java-tools, want het Java-model wordt niet bijgewerkt totdat de generieke resourcewijzigingslisteners worden geïnitieerd en het Java-model door de JDT-listeners wordt bijgewerkt met de nieuwe compilatie-eenheid.
Veel eenvoudige Java-broncodewijzigingen kunnen worden aangebracht met behulp van de API voor Java-elementen.
U kunt bijvoorbeeld een type van een compilatie-eenheid opvragen. Als u het IType hebt, kunt u protocollen zoals createField, createInitializer, createMethod of createType gebruiken om broncodeleden aan het type toe te voegen. De broncode en gegevens over de locatie van het lid worden in deze methoden aangeleverd.
Met de interface ISourceManipulation bevat definities voor veelgebruikte bronbewerkingen van Java-elementen. Hieronder vallen methoden voor het hernoemen, verplaatsen, kopiëren of verwijderen van elementen van een type.
Code kan worden gewijzigd door de compilatie-eenheid rechtstreeks te bewerken (hierbij wordt de onderliggende IFile gewijzigd) of door een exemplaar van de compilatie-eenheid in het geheugen te bewerken. Het laatste wordt een werkexemplaar genoemd.
Een werkexemplaar van een compilatie-eenheid wordt verkregen via de methode getWorkingCopy. (Opmerking: de compilatie-eenheid hoeft niet in het Java-model aanwezig te zijn om een werkexemplaar te maken.) Degene die het werkexemplaar maakt, moet het ook weer wissen met de methode discardWorkingCopy als het werkexemplaar niet meer nodig is.
Werkexemplaren worden bewerkt in een geheugenbuffer. Via de methode getWorkingCopy() wordt een standaardbuffer gemaakt, maar clients kunnen hun eigen bufferimplementatie aanleveren met behulp van de methode getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor). Clients kunnen de tekst in deze buffer rechtstreeks bewerken. Hierbij moeten zij het werkexemplaar tezijnertijd met de buffer synchroniseren met behulp van de methode reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor).
Ten slotte kan het werkexemplaar op schijf worden opgeslagen (hierbij wordt de oorspronkelijke compilatie-eenheid vervangen) met de methode commitWorkingCopy.
Zo wordt met de volgende code een werkexemplaar van een compilatie-eenheid gemaakt met de eigenaar van een aangepast werkexemplaar. In de code wordt eerst de buffer gewijzigd. Vervolgens worden de wijzigingen gesynchroniseerd en op schijf opgeslagen. Ten slotte wordt het werkexemplaar weer gewist.
// Oorspronkelijke compilatie-eenheid ophalen ICompilationUnit originalUnit = ...; // Eigenaar van werkexemplaar ophalen WorkingCopyOwner owner = ...; // Werkexemplaar maken ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // Buffer wijzigen en synchroniseren IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // Wijzigingen vastleggen workingCopy.commitWorkingCopy(false, null); // Werkexemplaar wissen workingCopy.discardWorkingCopy();
Werkexemplaren kunnen ook gedeeld worden gebruikt door verschillende clients met behulp van een werkexemplaareigenaar. Een werkexemplaar kan later worden opgehaald met de methode findWorkingCopy. Een gedeeld werkexemplaar is aldus gekoppeld aan de oorspronkelijke compilatie-eenheid en een werkexemplaareigenaar.
In het onderstaande voorbeeld ziet u hoe client 1 een gedeeld werkexemplaar maakt, client 2 dit werkexemplaar ophaalt, client 1 het werkexemplaar wist, waarna client 2 het gedeelde werkexemplaar probeert op te halen en merkt dat het niet meer bestaat:
// Client 1 & 2: oorspronkelijke compilatie-eenheid ophalen ICompilationUnit originalUnit = ...; // Client 1 & 2: werkexemplaareigenaar ophalen WorkingCopyOwner owner = ...; // Client 1: Gedeeld werkexemplaar maken ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // Client 2: Gedeeld werkexemplaar ophalen ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // Dit is hetzelfde werkexemplaar assert workingCopyForClient1 == workingCopyForClient2; // Client 1: Gedeeld werkexemplaar wissen workingCopyForClient1.discardWorkingCopy(); // Client 2: Mislukte poging om gedeeld werkexemplaar op te halen (werkexemplaar = null) workingCopyForClient2 = originalUnit.findWorkingCopy(owner); assert workingCopyForClient2 == null;
U kunt ook een nieuwe Compilatie-eenheid maken met de factorymethoden voor AST. De methodenamen beginnen met new.... Hieronder ziet u een voorbeeld voor de aanmaak van de klasse HelloWorld.
Het eerste stukje code is de gegenereerde uitvoer:
voorbeeldpakket;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hallo" + " wereld");
}
}
Het volgende stuk is de bijbehorende code waarmee de uitvoer wordt gegenereerd.
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("Hallo");
infixExpression.setLeftOperand(literal);
literal = ast.newStringLiteral();
literal.setLiteralValue(" wereld");
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 wordt gebruikt om de invoerbron in tokens te verdelen. Elk token heeft een specifieke waarde die is gedefinieerd in de interface ITerminalSymbols. Het is relatief eenvoudig om het juiste token te herhalen en op te halen. Het is raadzaam de scanner te gebruiken als u de positie van het sleutelwoord super in een instantie van SuperMethodInvocation zoekt.
Sommige broncodewijzigingen kunnen niet worden uitgevoerd via de API voor Java-elementen. In plaats daarvan kunt u ook (bijvoorbeeld als u de broncode van bestaande elementen wilt wijzigen) gebruik maken van de 'ruwe' broncode van de compilatie-eenheid en de herschrijf-API voor de DOM/AST.
Er zijn twee API's om de DOM/AST te herschrijven: de beschrijvings-API en de wijzigings-API.
Met de beschrijvings-API wordt de AST niet gewijzigd, maar wordt de API ASTRewrite gebruikt om de beschrijvingen van wijzigingen te genereren.
De beschrijvingen van wijzigingen in knooppunten worden door de AST-rewriter verzameld en vertaald naar tekstbewerkingen, die vervolgens kunnen worden toegepast op de oorspronkelijke bron.
// Document maken
ICompilationUnit cu = ... ; // Inhoud is "public class X {\n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// DOM/AST maken van ICompilationUnit
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// ASTRewrite maken
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// beschrijving van de wijziging
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// Tekstbewerkingen berekenen
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// Nieuwe broncode berekenen
edits.apply(document);
String newSource = document.get();
// Compilatie-eenheid bijwerken
cu.getBuffer().setContents(newSource);
Met de wijzigings-API kunt u de AST rechtstreeks bewerken:
// Document maken ICompilationUnit cu = ... ; // Inhoud is "public class X {\n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // DOM/AST maken van ICompilationUnit ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // Vastleggen van de wijzigingen beginnen astRoot.recordModifications(); // AST wijzigen TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // Tekstbewerkingen berekenen TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // Nieuwe broncode berekenen edits.apply(document); String newSource = document.get(); // Compilatie-eenheid bijwerken cu.getBuffer().setContents(newSource);
Als uw plugin achteraf moet worden bijgewerkt met wijzigingen in Java-elementen, kunt u een Java-listener IElementChangedListener registreren met JavaCore.
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
U kunt nog specifieker zijn en het type events opgeven waarin u bent geïnteresseerd met addElementChangedListener(IElementChangedListener, int).
Als u bijvoorbeeld alleen wilt luisteren naar events tijdens een synchronisatiebewerking, gebruikt u:
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
Er zijn twee soorten events die worden ondersteund door JavaCore:
Listeners voor wijzigingen in Java-elementen zijn vergelijkbaar met listeners voor resourcewijzigingen (zie resourcewijzigingen traceren). Met het volgende stuk code implementeert u een Java-elementwijzigingsreporter om de elementdelta's uit te voeren naar de systeemconsole.
public class MyJavaElementChangeReporter implements IElementChangedListener { public void elementChanged(ElementChangedEvent event) { IJavaElementDelta delta= event.getDelta(); if (delta != null) { System.out.println("delta ontvangen: "); System.out.print(delta); } } }
IJavaElementDelta bevat het gewijzigde element en vlaggen waarmee het soort aangebrachte wijziging wordt aangegeven. In de meeste gevallen wordt de deltastructuur toegepast op het niveau van het Java-model. Clients moeten door deze deltastructuur navigeren met getAffectedChildren om uit te zoeken welke projecten zijn gewijzigd.
In het onderstaande voorbeeld wordt een delta onderzocht en de toegevoegde, verwijderde en gewijzigde elementen afgedrukt:
void traverseAndPrint(IJavaElementDelta delta) { switch (delta.getKind()) { case IJavaElementDelta.ADDED: System.out.println(delta.getElement() + " is toegevoegd"); break; case IJavaElementDelta.REMOVED: System.out.println(delta.getElement() + " is verwijderd"); break; case IJavaElementDelta.CHANGED: System.out.println(delta.getElement() + " is gewijzigd"); if ((delta.getFlags() & IJavaElementDelta.F_CHILDREN) != 0) { System.out.println("De onderliggende elementen zijn gewijzigd"); } if ((delta.getFlags() & IJavaElementDelta.F_CONTENT) != 0) { System.out.println("De inhoud is gewijzigd"); } /* Andere vlaggen zijn ook mogelijk */ break; } IJavaElementDelta[] children = delta.getAffectedChildren(); for (int i = 0; i < children.length; i++) { traverseAndPrint(children[i]); } }
Een Java-elementwijzigingsmelding kan door verschillende bewerkingen worden geïnitieerd. Hieronder ziet u enkele voorbeelden:
Net als bij IResourceDelta kunnen de Java-elementdelta's groepsgewijs worden verwerkt met IWorkspaceRunnable. De delta's die ontstaan door verschillende Java-modelbewerkingen die in een IWorkspaceRunnable worden uitgevoerd, worden samengevoegd en tegelijk gemeld.
JavaCore biedt een runmethode voor groepsgewijze verwerking van Java-elementwijzigingen.
Zo worden met het volgende voorbeeldcodefragment twee Java-elementwijzigingsevents geïnitieerd:
// Pakket ophalen IPackageFragment pkg = ...; // Twee compilatie-eenheden maken ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
Met het volgende codefragment wordt echter één Java-elementwijzigingsevent geïnitieerd:
// Pakket ophalen IPackageFragment pkg = ...; // Twee compilatie-eenheden maken 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);