Ditt insticksprogram kan använda JDT-API:t till att skapa klasser eller gränssnitt, lägga till metoder till befintliga typer eller ändra metoderna för typer.
Det enklaste sättet att ändra Java-objekt är att använda Java-element-API:t. Mer allmänna tekniker kan användas till att arbeta med den råa källkoden för ett Java-element.
Det enklaste sättet att programmatiskt generera en kompileringsenhet är att använda IPackageFragment.createCompilationUnit. Du anger namnet och innehåller i kompileringsenheten. Kompileringsenheten skapas inuti paketet och den nya ICompilationUnit returneras.
Du kan skapa en kompileringsenhet generiskt genom att skapa en filresurs vars filtillägg är ".java" i den mapp som motsvarar paketkatalogen. Användning av det generiska resurs-API:t är en bakdörr till Java-verktygen. Det innebär att Java-modellen inte uppdateras förrän de generiska resursändringslyssnarna meddelas och JDT-lyssnarna uppdaterar Java-modellen med den nya kompileringsenheten.
De mest enkla modifieringar av Java-källkod kan köras med Java-element-API:t.
Du kan t.ex. fråga en typ från en kompileringsenhet. När du harIType kan du använda protokoll som createField, createInitializer, createMethod, eller createType för att lägga till källkodsmedlemmar i typen. Källkoden och information om platsen för medlemmen tillhandahålls i dessa metoder.
Gränssnittet ISourceManipulation definierar vanliga källkodsmanipulationer för Java-element. Det innefattar metoder för att byta namn på, flytta, kopiera och ta bort en typs medlem.
Du kan modifiera kod genom att manipulera kompileringsenheten (vilket därigenom modifierar den underliggande IFile) eller så kan du modifiera en kopia i minnet av kompileringsenheten, kallat en arbetskopia.
En arbetskopia erhålls från en kompileringsenhet med hjälp av metoden getWorkingCopy. (Obs! Kompileringsenheten behöver inte finns i Java-modellen för att en arbetskopia ska kunna skapas.) Vem som än skapar en sådan arbetskopia ansvarar för att den tas bort när den inte behövs längre med hjälp av metoden discardWorkingCopy.
Arbetskopior modifierar en buffert i minnet. Metoden getWorkingCopy() skapar en standardbuffert, med klienter kan tillhandahålla sig egen buffertimplementation med hjälp av metoden getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor). Klienter kan manipulera texten i denna buffert direkt. Om de gör det, måste de då och då synkronisera arbetskopian med bufferten med någon av metodernareconcile(int, boolean, WorkingCopyOwner, IProgressMonitor).
Slutligen går det att spara en arbetskopia på disk (och ersätta den ursprungliga kompileringsenheten) med metoden commitWorkingCopy.
Ett exempel: följande kodstycke skapar en arbetskopia på en kompileringsenhet med en anpassad ägare till arbetskopian. Stycket modifierar bufferten, stämmer av ändringarna, skickar ändringarna till disk och tar sedan slutligen bort arbetskopian.
// Get original compilation unit ICompilationUnit originalUnit = ...; // Get working copy owner WorkingCopyOwner owner = ...; // Create working copy ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // Modify buffer and reconcile IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // Commit changes workingCopy.commitWorkingCopy(false, null); // Destroy working copy workingCopy.discardWorkingCopy();
Det går också att dela arbetskopior mellan flera klienter genom att använda en ägare till arbetskopian. En arbetskopia kan hämtas vid ett senare tillfälle med hjälp av metoden findWorkingCopy. En delad arbetskopia kopplas därigenom till den ursprungliga kompileringsenheten och till en ägare.
Följande kodexempel visar hur klient 1 skapar en delad arbetskopia, klient 2 hämtar den, klient 1 kastar bort den och klient 2 försöker hämta den för att upptäcka att den inte finns längre:
// Client 1 & 2: Get original compilation unit ICompilationUnit originalUnit = ...; // Client 1 & 2: Get working copy owner WorkingCopyOwner owner = ...; // Client 1: Create shared working copy ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // Client 2: Retrieve shared working copy ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // This is the same working copy assert workingCopyForClient1 == workingCopyForClient2; // Client 1: Discard shared working copy workingCopyForClient1.discardWorkingCopy(); // Client 2: Attempt to retrieve shared working copy and find out it's null workingCopyForClient2 = originalUnit.findWorkingCopy(owner); assert workingCopyForClient2 == null;
Det går att skapa en Kompileringsenhet från noll med fabriksmetoderna i AST. Dessa metodnamn börjar med new.... Följande är ett exempel som skapar en klass med namnet HelloWorld.
Det första stycket är de genererade utdata:
package example;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello" + " world");
}
}
Följande stycke är motsvarande kod som genererar utdata.
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 används till att dela upp indatakällan i tokens. Varje token har ett specifikt värde som definieras i gränssnittetITerminalSymbols. Det är ganska enkelt att iterera och hämta rätt token. Vi rekommenderar även att du använder skannern om du vill leta upp positionen för nyckelordet super i enSuperMethodInvocation.
Vissa källkodsmodifieringar tillhandahålls inte via Java-element-API:t. Ett mer allmänt sätt att redigera källkod (t.ex. att ändra källkoden för befintliga elements) uppnås genom att använda kompileringsenhetens råa källkod och omskrivnings-API:t för DOM/AST.
Om du vill utföra DOM/AST-omskrivning, finns två uppsättningar API:er: den beskrivande omskrivningen och den modifierande omskrivningen.
Det beskrivande API:r modifierar inte AST men använder ASTRewrite-API:t till att generera modifieringarnas beskrivningar.
AST-omskrivaren samlar in beskrivningar av modifieringarna av noder och översätter dessa beskrivningar till textredigeringar som sedan kan tillämpas på den ursprungliga källkoden.
// creation of a Document
ICompilationUnit cu = ... ; // content is "public class X {\n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// creation of DOM/AST from a ICompilationUnit
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// creation of ASTRewrite
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// description of the change
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// computation of the text edits
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// computation of the new source code
edits.apply(document);
String newSource = document.get();
// update of the compilation unit
cu.getBuffer().setContents(newSource);
Det modifierande API:T gör det möjligt att modifiera AST direkt:
// creation of a Document ICompilationUnit cu = ... ; // content is "public class X {\n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // creation of DOM/AST from a ICompilationUnit ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // start record of the modifications astRoot.recordModifications(); // modify the AST TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // computation of the text edits TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // computation of the new source code edits.apply(document); String newSource = document.get(); // update of the compilation unit cu.getBuffer().setContents(newSource);
Om ditt insticksprogram behöver få mer information om ändringar av Java-element i efterhand kan du registrera en Java IElementChangedListener med JavaCore.
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
Du kan vara mer specifik och ange vilken typ av händelser du är intresserad av med addElementChangedListener(IElementChangedListener, int).
Ett exempel: om du bara är intresserad av att lyssna efter händelser under en avstämningsoperation:
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
Det finns två typer av händelser som hanteras av JavaCore:
Lyssnare av ändringar i Java-element är likartade konceptuellt till resursändringslyssnare (beskrivs i spåra resursändringar). Följande kodstycke implementerar en funktion som rapporterar ändringar av Java-element som skriver ut elementdeltan på systemkonsolen.
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 inkluderar det element som ändrades och flaggor som beskriver vilken typ av ändring som utfördes. Den mesta tiden är deltaträdet rotat på Java-modellnivå. Klienter måste sedan navigera i detta delta med getAffectedChildren för att få redan på vilka projekt som har ändrats.
Följande exempel stegar igenom ett delta och skriver ut de element som har lagts till, tagits bort eller ändrats:
void traverseAndPrint(IJavaElementDelta delta) { switch (delta.getKind()) { case IJavaElementDelta.ADDED: System.out.println(delta.getElement() + " was added"); break; case IJavaElementDelta.REMOVED: System.out.println(delta.getElement() + " was removed"); break; case IJavaElementDelta.CHANGED: System.out.println(delta.getElement() + " was changed"); if ((delta.getFlags() & IJavaElementDelta.F_CHILDREN) != 0) { System.out.println("The change was in its children"); } if ((delta.getFlags() & IJavaElementDelta.F_CONTENT) != 0) { System.out.println("The change was in its content"); } /* Others flags can also be checked */ break; } IJavaElementDelta[] children = delta.getAffectedChildren(); for (int i = 0; i < children.length; i++) { traverseAndPrint(children[i]); } }
Det finns flera typer av operationer som kan utlösa en avisering om ändrat Java-element. Här är några exempel:
På liknande sätt somIResourceDelta kan Java-elements deltan batchbearbetas med en IWorkspaceRunnable. De deltan som uppstår från flera Java-modelloperationer som körs i en IWorkspaceRunnable sammanfogas och rapporteras samtidigt.
JavaCore tillhandahåller metoden run för batchbearbetning av Java-elementändringar.
Ett exempel: följande kodfragment utlöser 2 händelser som ändrar Java-element:
// Get package IPackageFragment pkg = ...; // Create 2 compilation units ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
Till skillnad från följande kodfragment som utlöser 1 händelse som ändrar Java-element:
// Get package IPackageFragment pkg = ...; // Create 2 compilation units 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);