Lisäosa voi luoda luokkia tai rajapintoja, lisätä metodeja olemassa oleviin tyyppeihin tai muuttaa tyyppien metodeja JDT-sovellusohjelmaliittymän avulla.
Helpoin tapa muuttaa Java-objekteja on käyttää Java-elementin sovellusohjelmaliittymää. Yleisempien tekniikoiden avulla voidaan käsitellä Java-elementin muotoilematonta lähdekoodia.
Helpoin tapa luoda käännösyksikkö ohjelmallisesti on käyttää IPackageFragment.createCompilationUnit-metodia. Määrität käännösyksikön nimen ja sisällön. Käännösyksikkö luodaan paketin sisään ja uusi ICompilationUnit palautuu.
Käännösyksikön voi luoda yleisesti luomalla tiedostoresurssin, jonka tunniste on ".java", sopivaan kansioon, joka vastaa paketin hakemistoa. Yleisen resurssisovellusohjelmaliittymän käyttö on takaportti Java-työkaluihin niin, että Java-mallia ei päivitetä, ennen kuin yleisten resurssien muutoksen kuuntelutoiminnoille on lähetetty ilmoitus ja JDT-kuuntelutoiminnot päivittävät Java-mallin uudella käännösyksiköllä.
Yksinkertaisimmat Java-lähteen muutokset voi tehdä Java-elementin sovellusohjelmaliittymällä.
Voit esimerkiksi kysellä tyyppiä käännösyksiköstä. Kun IType-rajapinta on olemassa, voit lisätä lähdekoodin jäseniä tyyppiin esimerkiksi createField-, createInitializer-, createMethod-, tai createType-käytännöllä. Lähdekoodi ja jäsenen sijainnin tiedot sisältyvät näihin metodeihin.
ISourceManipulation-rajapinta määrittää Java-elementtien yleiset lähteen muutokset. Tämä sisältää metodit tyypin jäsenen uudelleennimeämiseen, siirtoon, kopiointiin ja poistoon.
Koodia voi muuttaa käsittelemällä käännösyksikköä (jolloin pohjalla oleva IFile muuttuu) tai muuttamalla muistissa olevaa käännösyksikön kopiota, jota kutsutaan työskentelykopioksi.
Työskentelykopio saadaan käännösyksiköstä getWorkingCopy-metodilla. (Huomaa, että käännösyksikön ei tarvitse olla Java-mallissa, että työskentelykopion voi luoda.) Kaikki työskentelykopion luojat ovat vastuussa sen poistosta, kun sitä ei enää tarvita, käyttämällä discardWorkingCopy-metodia.
Työskentelykopiot muuttavat muistissa olevaa puskuria. getWorkingCopy()-metodi luo oletuspuskurin, mutta työasemat voivat toteuttaa myös oman puskurinsa getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor)-metodilla. Työasemat voivat käsitellä tämän puskurin tekstiä suoraan. Jos näin tehdään, työskentelykopio on synkronoitava ajoittain reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor)-metodilla.
Lopuksi työskentelykopion voi tallentaa levyyn (jolloin se korvaa alkuperäisen käännösyksikön) commitWorkingCopy-metodilla.
Esimerkiksi seuraava koodikatkelma luo käännösyksikön työskentelykopion mukautetulla työskentelykopion omistajalla. Katkelma muuttaa puskuria, täsmäyttää muutokset, vahvistaa muutokset levyyn ja lopuksi poistaa työskentelykopion.
// Hae alkuperäinen käännösyksikkö ICompilationUnit originalUnit = ...; // Hae työskentelykopion omistaja WorkingCopyOwner owner = ...; // Luo työskentelykopio ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // Muuta puskuri ja täsmäytä IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // Vahvista muutokset workingCopy.commitWorkingCopy(false, null); // Tuhoa työskentelykopio workingCopy.discardWorkingCopy();
Työskentelykopiot voivat olla myös usean työaseman yhteiskäytössä työskentelykopion omistajan avulla. Työskentelykopion voi myöhemmin noutaa findWorkingCopy-metodilla. Yhteiskäytössä oleva työskentelykopio merkitään näin alkuperäiseen käännösyksikköön ja työskentelykopion omistajaan.
Seuraavassa näkyy, miten työasema 1 luo yhteisen työskentelykopion, työasema 2 noutaa tämän työskentelykopion, työasema 1 poistaa työskentelykopion ja yhteisen työskentelykopion noutoa yrittävä työasema 2 huomaa, ettei sitä ole enää olemassa:
// Client 1 & 2: Hae alkuperäinen käännösyksikkö ICompilationUnit originalUnit = ...; // Client 1 & 2: Hae työskentelykopion omistaja WorkingCopyOwner owner = ...; // Client 1: Luo yhteiskäytössä oleva työskentelykopio ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // Client 2: Nouda yhteiskäytössä oleva työskentelykopio ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // Tämä on sama työskentelykopio assert workingCopyForClient1 == workingCopyForClient2; // Client 1: Tuhoa yhteiskäytössä oleva työskentelykopio workingCopyForClient1.discardWorkingCopy(); // Client 2: Yritys noutaa yhteiskäytössä oleva työskentelykopio ja sen huomaaminen, että se on null workingCopyForClient2 = originalUnit.findWorkingCopy(owner); assert workingCopyForClient2 == null;
Käännösyksikön voi luoda tyhjästä käyttämällä AST-rakenteessa factory-metodeja. Näiden metodien nimen alussa on new.... Seuraava esimerkki luo HelloWorld-luokan.
Ensimmmäinen katkelma on luotu tuloste:
package example;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello" + " world");
}
}
Seuraava katkelma on vastaava koodi, joka muodostaa tulosteen.
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-sovellusohjelmaliittymää käytetään jakamaan syötelähde sanakkeiksi. Jokaisella sanakkeella on oma arvo, joka on määritetty ITerminalSymbols-rajapinnassa. On melko yksinkertaista iteroida ja noutaa oikea sanake. On suositeltavaa käyttää myös etsintäohjelmaa, jos haluat löytää super-avainsanan SuperMethodInvocation-lausekkeesta.
Joitakin lähdekoodin muutoksia ei voi tehdä Java-elementin sovellusohjelmaliittymällä. Yleisempi tapa muokata lähdekoodia (esimerkiksi muuttaa olemassa olevien elementtien lähdekoodia) on käyttää käännösyksikön muotoilematonta lähdekoodia ja kirjoittaa DOM/AST-solmun sovellusohjelmaliittymä uudelleen.
DOM/AST-solmun uudelleenkirjoitusta varten on kaksi sovellusohjelmaliittymää: kuvaava uudelleenkirjoitus ja muuttava uudelleenkirjoitus.
Kuvaava sovellusohjelmaliittymä ei muuta AST-solmua, vaan muodostaa muutosten kuvauksen ASTRewrite-sovellusohjelmaliittymän avulla.
AST-uudelleenkirjoitustoiminto kerää muutosten kuvaukset solmuihin ja kääntää kuvaukset tekstin muokkauksiksi, jotka voidaan tämän jälkeen toteuttaa alkuperäiseen lähteeseen.
// Asiakirjan luonti
ICompilationUnit cu = ... ; // sisältö on "public class X {\n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// DOM/AST-solmun luonti ICompilationUnit-käännösyksiköstä
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// ASTRewrite-luonti
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// muutoksen kuvaus
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// tekstin muokkausten laskenta
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// uuden lähdekoodin laskenta
edits.apply(document);
String newSource = document.get();
// käännösyksikön päivitys
cu.getBuffer().setContents(newSource);
Muuttava sovellusohjelmaliittymä mahdollistaa AST-solmun suoran muutoksen:
// Asiakirjan luonti ICompilationUnit cu = ... ; // sisältö on "public class X {\n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // DOM/AST-solmun luonti ICompilationUnit-käännösyksiköstä ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // aloita muutosten tallennus astRoot.recordModifications(); // muuta AST:tä TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // tekstin muokkausten laskenta TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // uuden lähdekoodin laskenta edits.apply(document); String newSource = document.get(); // käännösyksikön päivitys cu.getBuffer().setContents(newSource);
Jos lisäosan tarvitsee tietää Java-elementteihin tarkistuksen jälkeen tehdyistä muutoksista, voit rekisteröidä Javan IElementChangedListener-rajapinnan JavaCore-lisäosan avulla.
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
Voit määrittää tarkemmin kiinnostuksen kohteena olevien tapahtumien tyypin käyttämällä addElementChangedListener(IElementChangedListener, int) -metodia.
Jos olet esimerkiksi kiinnostunut vain täsmäytyksen aikaisten tapahtumien kuuntelusta:
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
JavaCore tukee kahta tapahtumalajia:
Java-elementin muutoksen kuuntelutoiminnot ovat periaatteessa samankaltaisia kuin resurssin muutoksen kuuntelutoiminnot (kuvattu ohjeaiheessa resurssin muutosten seuranta). Seuraava katkelma toteuttaa Java-elementin muutoksen ilmoitustoiminnon, joka tulostaa elementin deltat järjestelmäkonsoliin.
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 sisältää muutetun elementin ja määritteet, jotka kuvaavat tapahtuneen muutoksen lajia. Useimmiten deltarakenteen juuri on Java-mallin tasolla. Tällöin työasemien on selvitettävä muuttuneet projektit siirtymällä kyseiseen deltaan getAffectedChildren-metodin avulla.
Seuraava esimerkki kulkee deltan läpi ja tulostaa lisätyt, poistetut ja muutetut elementit:
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]); } }
Monenlaiset toiminnot voivat liipaista Java-elementin muutosilmoituksen. Seuraavassa on joitakin esimerkkejä:
Samon kuin IResourceDelta-deltoja, Java-elementtideltoja voi asettaa eräajoon IWorkspaceRunnable-rajapinnan avulla. Useiden IWorkspaceRunnable-rajapinnan sisällä ajettujen Java-mallitoimintojen tuloksena olevat deltat yhdistetään ja ilmoitetaan kerralla.
JavaCore sisältää run-metodin, jolla Java-elementin muutokset voi asettaa eräajoon.
Esimerkiksi seuraava koodikatkelma liipaisee 2 Java-elementin muutostapahtumaa:
// Hae paketti IPackageFragment pkg = ...; // Luo 2 käännösyksikköä ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
Seuraava koodikatkelma puolestaan liipaisee 1 Java-elementin muutostapahtuman:
// Hae paketti IPackageFragment pkg = ...; // Luo 2 käännösyksikköä 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);