Plugin-modulen kan bruke JDT-APIet til å opprette klasser eller grensesnitt, legge til metoder i eksisterende typer eller endre metodene for typer.
Den enkleste måten å endre Java-objekter på er å bruke Java-elementets API. Flere generelle teknikker kan brukes når du arbeider med rå kildekode for et Java-element.
Den enkleste måten å generere en kompileringsenhet programmatisk på er å bruke IPackageFragment.createCompilationUnit. Du oppgir navnet på og innholdet i kompileringsenheten. Kompileringsenheten opprettes inne i pakken, og den nye ICompilationUnit returneres.
En kompileringsenhet kan opprettes generisk ved at det opprettes en filressurs med filtype ".java" i riktig mappe som svarer til pakkekatalogen. Bruk av generisk ressurs-API er en bakdør inn til bruk av Java-verktøy, så Java-modellen oppdateres ikke før den generiske ressursens endringslyttere underrettes og JDT-lytterne oppdaterer Java-modellen med den nye kompileringsenheten.
De fleste enkle endringer av Java-kilde kan utføres ved hjelp av Java-elementets API.
Du kan for eksempel spørre om en type fra en kompileringsenhet. Når du har IType, kan du bruke protokoller som createField, createInitializer, createMethod, or createType til å legge til kildekodemedlemmer i typen. Kildekoden og informasjon om plasseringen til medlemmet følger med disse metodene.
Grensesnittet ISourceManipulation definerer vanlige kildemanipulasjoner for Java-elementer. Dette inkluderer metoder for navneendring, flytting, kopiering eller sletting av en types medlem.
Kode kan endres ved at kompileringsenheten (og dermed den underliggende IFile) endres, eller man kan endre en kopi i minnet av kompileringsenheten, som kalles en arbeidskopi.
En arbeidskopi hentes fra en kompileringsenhet ved hjelp av metoden getWorkingCopy. (Merk at kompileringsenheten ikke behøver å finnes i Java-modellen for at en arbeidskopi skal opprettes.) Den som oppretter en slik arbeidskopi, er ansvarlig for å fjerne den når den ikke lenger er nødvendig, ved hjelp av metoden discardWorkingCopy.
Arbeidskopier endrer en minnebuffer. Metoden getWorkingCopy() oppretter en standardbuffer, men klienter kan bruke sin egen bufferimplementering ved hjelp av metoden getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor). Klienter kan manipulere teksten i denne bufferen direkte. Hvis de gjør det, må de synkronisere arbeidskopien med bufferen en gang imellom ved hjelp av metoden reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor).
En arbeidskopi kan dessuten lagres til disk (og erstatte den opprinnelige kompileringsenheten) ved hjelp av metoden commitWorkingCopy.
For eksempel oppretter kodesnutten nedenfor en arbeidskopi på en kompileringsenhet med en tilpasset arbeidskopieier. Snutten endrer bufferen, avstemmer endringene, setter i verk endringene på disk og fjerner arbeidskopien.
// Hent opprinnelig kompileringsenhet ICompilationUnit originalUnit = ...; // Hent arbeidskopieier WorkingCopyOwner owner = ...; // Opprett arbeidskopi ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // Endre buffer og avstem IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // Iverksett endringer workingCopy.commitWorkingCopy(false, null); // Slett arbeidskopi workingCopy.discardWorkingCopy();
Arbeidskopier kan også deles av flere klienter ved hjelp av en arbeidskopieier. En arbeidskopi kan senere hentes inn ved hjelp av metoden findWorkingCopy. En delt arbeidskopi tastes inn på den opprinnelige kompileringsenheten og på arbeidskopieieren.
Følgende viser hvordan klient 1 oppretter en delt arbeidskopi, klient 2 henter denne arbeidskopien, klient 1 sletter arbeidskopien, og klient 2 prøver å hente den delte arbeidskopien og merker at den ikke lenger finnes:
// Klient 1 & 2: Hent opprinnelig kompileringsenhet ICompilationUnit originalUnit = ...; // Klient 1 & 2: Hent arbeidskopieier WorkingCopyOwner owner = ...; // Klient 1: Opprett delt arbeidskopi ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // Klient 2: Hent delt arbeidskopi ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // Samme som arbeidskopi assert workingCopyForClient1 == workingCopyForClient2; // Klient 1: Slett delt arbeidskopi workingCopyForClient1.discardWorkingCopy(); // Klient 2: Forsøk på å hente inn delt arbeidskopi, ser at den er null workingCopyForClient2 = originalUnit.findWorkingCopy(owner); assert workingCopyForClient2 == null;
Det er mulig å opprette en CompilationUnit fra grunnen av ved hjelp av factory-metoder på AST. Disse metodenavnene begynner med new.... Her er et eksempel som oppretter klassen HelloWorld.
Første snutt er genererte utdata:
package example;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello" + " world");
}
}
Følgende snutt er tilsvarende kode som genererer utdataene:
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 brukes til å dele inndatakilden i symboler. Hvert symbol har en bestemt verdi som defineres i grensesnittet ITerminalSymbols. Det er ganske enkelt å gjenta og hente riktig symbol. Vi kan også anbefale at du brukeren skanneren hvis du vil finne posisjonen til nøkkelordet super i en SuperMethodInvocation.
Noen kildekodeendringer følger ikke med Java-element-APIet. En mer generell måte å redigere kildekoden på (for eksempel endre kildekoden for eksisterende elementer) får du ved å bruke kompileringsenhetens råkildekode og skrive om APIet for DOM/AST.
Når du skal utføre DOM/AST-omskriving, er det to sett med APIer: deskriptiv omskriving av endrende omskriving.
APIer for deskriptiv omskriving ender ikke AST, men bruker APIet ASTRewrite
til å generere beskrivelsene av endringene.
AST-omskriveren samler inn beskrivelser av endringer på noder og oversetter disse beskrivelsene til tekstredigeringer, som så kan brukes på
originalkilden.
// Opprett dokument
ICompilationUnit cu = ... ; // Innhold er "public class X {\n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// Opprett DOM/AST fra en ICompilationUnit
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// Opprett ASTRewrite
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// Beskrivelse av endringen
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// Beregning av tekstredigeringer
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// Beregning av ny kildekode
edits.apply(document);
String newSource = document.get();
// Oppdater kompileringsenhet
cu.getBuffer().setContents(newSource);
Endrende API gjør det mulig å endre AST direkte:
// Opprett dokument ICompilationUnit cu = ... ; // Innhold er "public class X {\n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // Opprett DOM/AST fra en ICompilationUnit ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // Start registrering av endringer astRoot.recordModifications(); // Endre AST TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // Beregning av tekstredigeringer TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // Beregning av ny kildekode edits.apply(document); String newSource = document.get(); // Oppdater kompileringsenhet cu.getBuffer().setContents(newSource);
Hvis plugin-modulen må vite om endringer i Java-elementer etter at de har skjedd, kan du for registrere en Java-IElementChangedListener med JavaCore.
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
Du kan være mer spesifikk og oppgi typen hendelser du er interessert i, med addElementChangedListener(IElementChangedListener, int).
Hvis du for eksempel bare er interessert i å lytte etter hendelser under en avstemmingsoperasjon:
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
Det er to typer hendelser som støttes av JavaCore:
Java-elementendringslyttere er i prinsippet ganske like ressursendringslyttere (beskrevet i spore resultatendringer). Følgende snutt implementerer en endringsreporter for Java-element som skriver ut elementdeltaer på 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 inkluderer element, som er endret, og flagg som beskriver typen endring som har funnet sted. Det meste av tiden har deltatreet rot i Java-modellnivået. Klienter må så navigere i denne deltaen med getAffectedChildren for å finne ut hvilke prosjekter som er endret.
Følgende metodeeksempel traverserer en delta og skriver ut elementene som er lagt til, fjernet og endret:
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 flagg kan også sjekkes */ break; } IJavaElementDelta[] children = delta.getAffectedChildren(); for (int i = 0; i < children.length; i++) { traverseAndPrint(children[i]); } }
Flere typer operasjoner kan utløse et endringsvarsel for Java-element. Her er noen eksempler:
I likhet med IResourceDelta kan Java-elementdeltaer kjøres satsvist ved hjelp av en IWorkspaceRunnable. Deltaene man får fra flere Java-modelloperasjoner som kjøres inne i en IWorkspaceRunnable, slås sammen og rapporteres under ett.
JavaCore sørger for en run-metode for satsvis kjøring av Java-elementendringer.
For eksempel vil følgende kodefragment utløse to endringshendelser for Java-elementer:
// Hent pakke IPackageFragment pkg = ...; // Opprett to kompileringsenheter ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
Følgende kodefragment vil utløse en endringshandling for Java-elementer:
// Hent pakke IPackageFragment pkg = ...; // Opprett to kompileringsenheter 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);