Din plugin kan bruge JDT API'et til at oprette klasser eller grænseflader, tilføje metoder til eksisterende typer, eller ændre typernes metoder.
Den enkleste måde at ændre Java-objekter er at bruge Java-elementets API. Flere generelle teknikker kan anvendes til at arbejde med den ubehandlede kildekode til et Java-element.
Den nemmeste måde at generere en kompileringsenhed programmatisk er at bruge IPackageFragment.createCompilationUnit. Du angiver kompileringsenhedens navn og indhold. Kompileringsenheden oprettes i pakken og den nye ICompilationUnit returneres.
En kompileringsenhed kan oprettes generisk, ved at du opretter en filressource med filtypen ".java" i den relevante folder, som svarer til pakkebiblioteket. Anvendelsen af den generiske ressources API er som en bagdør til Java-værktøjerne, så Java-modellen opdateres ikke, før lytterne efter ændringer af den generiske ressource får besked, og JDT-lytterne opdaterer Java-modellen med den nye kompileringsenhed.
De enkleste Java-kildeændringer kan foretages via Java-elementets API.
Du kan f.eks. forespørge om en type fra en kompileringsenhed. Når du har IType, kan du bruge protokoller som f.eks. createField, createInitializer, createMethod eller createType til at tilføje kildekodemedlemmer til typen. Kildekoden og oplysninger om placeringen af medlemmet findes i disse metoder.
Grænsefladen ISourceManipulation definerer generelle kildemanipulationer for Java-elementer, herunder metoder til at omdøbe, flytte, kopiere eller slette en types medlem.
Kode kan ændres ved at manipulere kompileringsenheden (og sådan ændres også den underliggende IFile), eller du kan ændre en kopi i hukommelsen af kompileringsenheden, som kaldes en arbejdskopi.
En arbejdskopi hentes fra en kompileringsenhed ved hjælp af metoden getWorkingCopy. Bemærk, at kompileringsenheden ikke nødvendigvis skal være i Java-modellen, for at arbejdskopien skal kunne oprettes. Den, som opretter en sådan arbejdskopi, er ansvarlig for at slette den, når der ikke længere er brug for den, ved hjælp af metoden discardWorkingCopy.
Arbejdskopier ændrer en buffer i hukommelsen. Med metoden getWorkingCopy() oprettes en standardbuffer, men klienter kan stille deres egen bufferimplementering til rådighed ved hjælp af metoden getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor). Klienter kan manipulere teksten i denne buffer direkte. Hvis de gør det, skal de synkronisere arbejdskopien med bufferen fra tid til anden ved hjælp af metoden reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor).
Endelig kan en arbejdskopi gemmes på disken (hvorved den oprindelige kompileringsenhed erstattes) ved hjælp af metoden commitWorkingCopy.
Nedenstående kodestykke opretter f.eks. en arbejdskopi i en kompileringsenhed via en tilpasset arbejdskopiejer. Kodestykket ændrer bufferen, afstemmer ændringerne, committer ændringerne til disken og sletter til slut arbejdskopien.
// Hent oprindelig kompileringsenhed ICompilationUnit originalUnit = ...; // Hent arbejdskopiejer WorkingCopyOwner owner = ...; // Opret arbejdskopi ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // Revidér buffer, og afstem IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // Commit ændringer workingCopy.commitWorkingCopy(false, null); // Slet arbejdskopi workingCopy.discardWorkingCopy();
Flere klienter, der bruger en arbejdskopiejer, kan også være fælles om arbejdskopier. En arbejdskopi kan senere hentes ved hjælp af metoden findWorkingCopy. En fælles arbejdskopi indtastes således i den oprindelige kompileringsenhed og i en arbejdskopiejer.
Nedenfor vises, hvordan klient 1 opretter en fælles arbejdskopi, klient 2 henter denne arbejdskopi, klient 1 sletter arbejdskopien, og klient 2 prøver at hente bemærkningerne til den fælles arbejdskopi, der ikke findes mere:
// Klient 1 & 2: Hent oprindelig kompileringsenhed ICompilationUnit originalUnit = ...; // Klient 1 & 2: Hent arbejdskopiejer WorkingCopyOwner owner = ...; // Klient 1: Opret fælles arbejdskopi ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // Klient 2: Hent fælles arbejdskopi ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // Dette er den samme arbejdskopi assert workingCopyForClient1 == workingCopyForClient2; // Klient 1: Slet fælles arbejdskopi workingCopyForClient1.discardWorkingCopy(); // Klient 2: Forsøg at hente fælles arbejdskopi, og find ud af, at den er NULL workingCopyForClient2 = originalUnit.findWorkingCopy(owner); assert workingCopyForClient2 == null;
Det er muligt at oprette en CompilationUnit fra bunden ved hjælp af fabriksmetoderne i AST. Disse metoder begynder med new.... Nedenfor vises et eksempel, hvor klassen HelloWorld oprettes.
Det første kodestykke er det genererede output:
package example;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello" + " world");
}
}
Følgende kodestykke indeholder den tilsvarende kode, som genererer outputtet.
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 bruges til at dele inputkilden i tokens. Hver token har en bestemt værdi, som defineres i grænsefladen ITerminalSymbols. Det er ret enkelt at gentage og hente den rigtige token. Det anbefales desuden, at du bruger scanneren, hvis du vil finde positionen for nøgleordet super i en SuperMethodInvocation.
Visse kildekodeændringer stilles ikke til rådighed via Java-elementets API. Du kan redigere kildekode, f.eks. ændre kildekoden for eksisterende elementer, på en mere generel måde ved at bruge kompileringsenhedens ubehandlede kildekode og derefter omskrive API'et for DOM/AST.
Der er to sæt API'er til omskrivning af DOM/AST: det beskrivende og det ændrende API.
Det beskrivende API ændrer ikke AST'et, men bruger API'et
ASTRewrite
til at generere beskrivelserne af ændringerne.
AST-omskrivningsfunktionen opsamler beskrivelser af nodeændringer og konverterer
beskrivelserne til redigeringstekster, der derefter kan anvendes på den oprindelige
kilde.
// oprettelse af et dokument
ICompilationUnit cu = ... ; // content is "public class X {\n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// oprettelse af DOM/AST fra en ICompilationUnit
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// oprettelse af ASTRewrite
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// beskrivelse af ændringen
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// beregning af de redigeringsteksterne
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// beregning af den nye kildekode
edits.apply(document);
String newSource = document.get();
// opdatering af kompileringsenheden
cu.getBuffer().setContents(newSource);
Via det ændrende API kan AST'et ændres direkte:
// oprettelse af et dokument ICompilationUnit cu = ... ; // content is "public class X {\n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // oprettelse af DOM/AST fra en ICompilationUnit ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // start registrering af ændringerne astRoot.recordModifications(); // revidering af AST TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // beregning af de redigeringsteksterne TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // beregning af den nye kildekode edits.apply(document); String newSource = document.get(); // opdatering af kompileringsenheden cu.getBuffer().setContents(newSource);
Hvis din plugin har brug for at kende til ændringer af Java-elementer, efter de er foretaget, kan du registrere en Java IElementChangedListener med JavaCore.
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
Du kan gøre det mere specifikt og angive den type aktivitet, du er interesseret i at bruge addElementChangedListener(IElementChangedListener, int).
Hvis du f.eks. kun er interesseret i at lytte efter aktiviteter under en afstemningsfunktion:
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
JavaCore understøtter to slags aktiviteter:
Lyttere til Java-elementændringer ligner begrebsmæssigt lyttere til ressourceændringer (der beskrives under Spor ressourceændringer). Følgende kodestykke implementerer en funktion til rapportering af Java-elementændringer, som udskriver elementdeltaerne til systemkonsollen.
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 indeholder det element, der er blevet ændret, og markeringer, der beskriver, hvilken slags ændring der er foretaget. Oftest er deltatræstrukturens rod på Java-modelniveau. Klienter skal derefter navigere i deltaet ved hjælp af getAffectedChildren for at finde ud af, hvilke projekter der er blevet ændret.
Med følgende eksempelmetode gennemgås deltaen, og der bliver udskrevet elementer, der er tilføjet, fjernet og ændret:
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"); } /* Andre markeringer kan også blive komtrolleret */ break; } IJavaElementDelta[] children = delta.getAffectedChildren(); for (int i = 0; i < children.length; i++) { traverseAndPrint(children[i]); } }
Flere slags funktioner kan udløse en besked om Java-elementændring. Eksempler:
Ligner IResourceDelta, ved at Java-elementdeltaerne kan udføres i baggrunden ved hjælp af en IWorkspaceRunnable. De deltaer, der er resultat af flere Java-modelfunktioner, som udføres i en IWorkspaceRunnable, flettes og rapporteres på én gang.
JavaCore indeholder metoden Udfør til udførsel af Java-elementændringer i baggrunden.
For eksempel vil følgende kodefragment udløse to funktioner til Java-elementændring:
// Hent pakke IPackageFragment pkg = ...; // Opret to kompileringsenheder ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
Mens følgende kodefragment vil udløse én aktivitet til Java-elementændring:
// Hent pakke IPackageFragment pkg = ...; // Opret to kompileringsenheder 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);