您的外掛程式可使用 JDT API 來建立類別或介面、新增方法至現存的類型、或改變 類型的方法。
改變 Java 物件最簡單的方法就是使用 Java 元素 API。 尚有更多一般技術可用於一個 Java 元素的程式碼。
以程式設計的方式產生一個編譯單元最簡單的方法就是使用 IPackageFragment.createCompilationUnit。您要指定編譯單元的名稱和內 容。 編譯單元是建立於套件內,且會傳回新的 ICompilationUnit。
一般可在對應於套件目錄的適當資料夾中建立一個檔案資源來建立編譯單元,檔案資源 的副檔名是 ".java"。使用通用資源 API 是 Java 工具的 「後門」作業,所以在通用資源變更接聽器被通知且 JDT 接聽器以新的 編譯單元來更新 Java 模型之前,不會更新 Java 模型。
對於 Java 程式檔的簡單修正,大多可使用 Java 元素 API 來完成。
例如,您可從編譯單元來查詢類型。您一旦有 IType,可使用通訊協定,例如 createField、 createInitializer、 createMethod 或 createType ,將程式碼成員新增至類型。程式碼及關於成員位置的資訊是在這些方法中提供。
ISourceManipulation 介面定義 Java 元素的共用程式檔操作。這包含重新命名、移動、複製或刪除類型成員的方法。
程式碼可以藉由操作編譯單元來修改(如此一來,會修改基礎的 IFile) ,或者可以修改稱為工作副本之編譯單元的記憶體。
使用 getWorkingCopy 方法從編譯單元中取得工作副本。(請注意,編譯單元不需要存在於 Java 模型,就可以建立工作副本。) 建立這種工作複本的人,當不再使用它時,要負責使用 discardWorkingCopy 方法捨棄它。
工作副本修改記憶體中緩衝區。getWorkingCopy() 方法建立預設緩衝區,但是用戶端可使用 getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor) 方法提供自己的緩衝區實作。用戶端可以直接操作這個緩衝區的文字。 如果要這樣做,他們必須偶爾使用 reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor) 方法,同步化工作複本與緩衝區。
最後,可使用 commitWorkingCopy 方法將工作複本儲存至磁碟(取代原始編譯單元)。
例如,下列程式碼片段使用自訂工作複本擁有者在編譯單元上建立工作複本。片段會修改緩衝區、使變更一致、 並在磁碟上確定變更,最後捨棄工作複本。
// 取得原始編譯單元 ICompilationUnit originalUnit = ...; // 取得工作複本擁有者 WorkingCopyOwner owner = ...; // 建立工作複本 ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // 修改緩衝區並使其一致 IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // 確定變更 workingCopy.commitWorkingCopy(false, null); // 損毀工作複本 workingCopy.discardWorkingCopy();
工作複本可以由一些用戶端利用工作複本擁有者加以共用。稍後可使用 findWorkingCopy 方法來擷取工作複本。於是共用的工作複本在原始編譯單元和工作複本擁有者上含有索引鍵。
下列顯示用戶端 1 如何建立共用的工作複本,用戶端 2 擷取這個工作複本,用戶端 1 捨棄工作複本,而嘗試擷取共用工作複本的用戶端 2 注意到這個複本已不存在:
// 用戶端 1 & 2:取得原始編譯單元 ICompilationUnit originalUnit = ...; // 用戶端 1 & 2:取得工作複本擁有者 WorkingCopyOwner owner = ...; // 用戶端 1:建立共用的工作複本 ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // 用戶端 2:擷取共用的工作複本 ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // 這是相同的工作複本 assert workingCopyForClient1 == workingCopyForClient2; // 用戶端 1:捨棄共用的工作複本 workingCopyForClient1.discardWorkingCopy(); // 用戶端 2:試圖擷取共用的工作複本,卻發現它的空值 workingCopyForClient2 = originalUnit.findWorkingCopy(owner); assert workingCopyForClient2 == null;
可以使用 AST 中的 Factory 方法,從頭開始建立 CompilationUnit。 這些方法名稱開始於新建...。下列是建立 HelloWorld 類別的範例。
第一個片段為所產生的輸出:
套件範例;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello" + " world");
}
}
下列片段為產生的輸出所對應的程式碼。
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 是用來分割輸入來源為記號。 每一個記號有一個特定的值,定義在 ITerminalSymbols 介面中。 要重複並擷取正確的記號是非常容易的。我們也建議您使用掃描器,如果您想要找到 SuperMethodInvocation 中 super 關鍵字的正確位置。
Java 元素 API 不提供某些程式碼修正。編輯程式碼(例如變更現存元素的程式碼) 有一個更普通的方法,就是使用編譯單元的程式碼和 DOM/AST 的重寫 API。
如果要執行 DOM/AST 重寫,有兩組 API 可用:描述式重寫及修改式重寫。
描述式 API 不修改 AST,但使用 ASTRewrite API 來產生修改的說明。AST 重寫者收集節點修正的說明,將這些說明轉換成之後可以套用至原始程式碼的文字編輯。
// 建立文件
ICompilationUnit cu = ... ; // 內容是 "public class X {\n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// 從 ICompilationUnit 建立 DOM/AST
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// ASTRewrite 的建立
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// 變更的說明
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// 文字編輯的計算
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// 新程式碼的計算
edits.apply(document);
String newSource = document.get();
// 編譯單元的更新
cu.getBuffer().setContents(newSource);
修改的 API 容許直接修改 AST:
// 建立文件 ICompilationUnit cu = ... ; // 內容是 "public class X {\n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // 從 ICompilationUnit 建立 DOM/AST ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // 開始記錄修正 astRoot.recordModifications(); // 修改 AST TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // 文字編輯的計算 TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // 新程式碼的計算 edits.apply(document); String newSource = document.get(); // 編譯單元的更新 cu.getBuffer().setContents(newSource);
如果您的外掛程式需要知道 Java 元素在形成之後的變動,您可將一個 Java IElementChangedListener 登錄到 JavaCore。
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
您可以使用 addElementChangedListener(IElementChangedListener, int) 更專注的指定您有興趣的事件類型。
比方說,如果您只對一致作業期間接聽事件感興趣:
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
JavaCore 所支援的事件有兩種:
Java 元素變更接聽器的概念類似資源變更接聽器(說明於 追蹤資源變更)。下列片段實作一個 Java 元素變更報告器,可列印元素差距到 系統主控台。
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 包括 已變更的 element, 而 flags 說明所發生的變更類型。 大部分的時間,數據樹狀結構位在 Java 模型層次的根目錄。用戶端必需使用 getAffectedChildren 導覽這個數據以找出哪些專案已變更。
下列範例方法橫跨數據並列印新增、移除和變更的元素:
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]); } }
一些作業類型可以觸發 Java 元素變更通知。這裡是一些範例:
類似於 IResourceDelta, Java 元素數據可使用 IWorkspaceRunnable 進行批次處理。 一些在 IWorkspaceRunnable 中所執行的 Java 模型作業的數據會一起合併及產生報告。
JavaCore 提供 run 方法,以批次方式進行 Java 元素變更。
例如,下列程式碼片段將觸發 2 個 Java 元素變更事件:
// 取得套件 IPackageFragment pkg = ...; // 建立 2 個編譯單元 ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
下列程式碼片段則將觸發 1 個 Java 元素變更事件:
// 取得套件 IPackageFragment pkg = ...; // 建立 2 個編譯單元 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);