運用 Java 程式碼

您的外掛程式可使用 JDT API 來建立類別或介面、新增方法至現存的類型、或改變 類型的方法。

改變 Java 物件最簡單的方法就是使用 Java 元素 API。 尚有更多一般技術可用於一個 Java 元素的原始碼。

使用 Java 元素來修正程式碼

產生編譯單元

以程式設計的方式產生一個編譯單元最簡單的方法就是使用 IPackageFragment.createCompilationUnit。您要指定編譯單元的名稱和內 容。 編譯單元是建立於套件內,且會傳回新的 ICompilationUnit

一般可在對應於套件目錄的適當資料夾中建立一個檔案資源來建立編譯單元,檔案資源 的副檔名是 ".java"。使用通用資源 API 是 Java 工具的 「後門」作業,所以在通用資源變更接聽器被通知且 JDT 接聽器以新的 編譯單元來更新 Java 模型之前,不會更新 Java 模型。

修改編譯單元

對於 Java 原始檔的簡單修正,大多可使用 Java 元素 API 來完成。

例如,您可從編譯單元來查詢類型。您一旦有 IType,可使用通訊協定,例如 createFieldcreateInitializercreateMethodcreateType ,將原始碼成員新增至類型。原始碼及關於成員位置的資訊是在這些方法中提供。

ISourceManipulation 介面定義 Java 元素的共用原始檔操作。這包含重新命名、移動、複製或刪除類型成員的方法。

工作副本

程式碼可以藉由操作編譯單元來修改(如此一來,會修改基礎的 IFile) ,或者可以修改稱為工作副本之編譯單元的記憶體。

使用 getWorkingCopy 方法從編譯單元中取得工作副本。(請注意,編譯單元不需要存在於 Java 模型,就可以建立工作副本。)建立這種工作副本的人負責在不再使用時, 使用 destroy 方法毀損工作副本。

工作副本修改記憶體中緩衝區。 getWorkingCopy() 方法建立預設的緩衝區,但是用戶端可使用 getWorkingCopy(IProgressMonitor, IBufferFactory, IProblemRequestor) 方法自行提供緩衝區的實作。用戶端可以直接操作這個緩衝區的文字。 如果這樣做,他們必須使用 reconcile() 方法或 reconcile(boolean,IProgressMonitor) 方法同步化工作副本和緩衝區。

最後,可以使用 commit 方法將工作副本儲存到磁碟中(取代原始編譯單元)。   

例如,下列程式碼片段使用自訂緩衝區 Factory 建立編譯單元的工作副本。片段修改緩衝區、使變更一致、 並確認變更至磁碟中,最後毀損工作副本。

    // Get original compilation unit
    ICompilationUnit originalUnit = ...;
    
    // Get buffer factory
    IBufferFactory factory = ...;
    
    // Create working copy
    IWorkingCopy workingCopy = (IWorkingCopy)originalUnit.getWorkingCopy(null, factory, null);
    
    // Modify buffer and reconcile
    IBuffer buffer = ((IOpenable)workingCopy).getBuffer();
    buffer.append("class X {}");
    workingCopy.reconcile();
    
    // Commit changes
    workingCopy.commit(false, null);
    
    // Destroy working copy
    workingCopy.destroy();

工作副本可以由一些用戶端所共用。共用的工作副本使用 getSharedWorkingCopy 方法所建立,稍後可以使用 findSharedWorkingCopy 方法來擷取。共用的工作副本是含索引鍵的原始編譯單元和在緩衝區 Factory 中。

下列顯示用戶端 1 如何建立共用的工作副本,用戶端 2 擷取這個工作副本,用戶端 1 毀損工作副本,而用戶端 2 嘗試擷取共用工作副本,注意到此副本已不存在:

    // Client 1 & 2: Get original compilation unit
    ICompilationUnit originalUnit = ...;
    
    // Client 1 & 2: Get buffer factory
    IBufferFactory factory = ...;
    
    // Client 1: Create shared working copy
    IWorkingCopy workingCopyForClient1 = (IWorkingCopy)originalUnit.getSharedWorkingCopy(null, factory, null);
    
    // Client 2: Retrieve shared working copy
    IWorkingCopy workingCopyForClient2 = (IWorkingCopy)originalUnit.findSharedWorkingCopy(factory);
     
    // This is the same working copy
    assert workingCopyForClient1 == workingCopyForClient2;
    
    // Client 1: Destroy shared working copy
    workingCopyForClient1.destroy();
    
    // Client 2: Attempt to retrieve shared working copy and find out it's null
    workingCopyForClient2 = (IWorkingCopy)originalUnit.findSharedWorkingCopy(factory);
    assert workingCopyForClient2 == null;

使用 DOM/AST API 來修正程式碼

有三個方式可建立 CompilationUnit。 第一個是使用現有的編譯單元。第二個方式是使用現有的類別檔。 第三個方式是使用 AST(抽象語法樹狀結構)中的 Factory 方法從頭開始。

從現有的編譯單元建立 AST

這是由 AST 上的剖析方法所達成: 所有的方法會適當的在結果樹狀結構中設定每一個節點的位置。連結的解析必須在建立樹狀結構前要求。 解析連結是是費時的作業,應該只在必要時完成。 一旦修改完成樹狀結構,會遺失所有位置和連結。

從現有的類別檔建立 AST

這是由 AST 上的剖析方法所達成: 這個方法會適當的在結果樹狀結構中設定每一個節點的位置。連結的解析必須在建立樹狀結構前要求。 解析連結是是費時的作業,應該只在必要時完成。 一旦修改完成樹狀結構,會遺失所有位置和連結。

從頭開始

可以使用 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);

擷取額外位置

DOM/AST 節點只包含一對位置(起始位置和節點的長度)。這不是永遠足夠的。 為了擷取中間的位置,應該使用 IScanner API。 例如,我們有 InstanceofExpression ,其中我們想要知道 instanceof 運算子的位置。 我們可以撰寫下列方法達成此目的:
	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 介面中。 要重複並擷取正確的記號是非常容易的。我們也建議您使用掃描器,如果您想要找到 SuperMethodInvocationsuper 關鍵字的正確位置。

通用原始碼修正

Java 元素 API 不提供某些原始碼修正。編輯原始碼(例如變更現存元素的原始碼) 有一個更普通的方法,就是使用編譯單元的原始碼和 Java DOM。

這些技術包括:

// 取得編譯單元的原始檔 String contents = myCompilationUnit.getBuffer().getContents();
// 建立可編輯的 JDOMmyJDOM = new DOMFactory();   myDOMCompilationUnit = myJDOM.createCompilationUnit(contents, "MyClass");

// 如要導覽和編輯編譯單元結構,則使用    // JDOM 節點通訊協定。
   ...
// 一旦對所有節點完成修改,// 則從編譯單元 DOM 節點取回原始檔。String newContents = myDOMCompilationUnit.getContents();
// 將此程式碼設回編譯單元元素myCompilationUnit.getBuffer().setContents(newContents);
// 將緩衝區儲存到檔案。
myCompilationUnit.save(null, false);

此技術可能導致問題標示元與不正確的行號建立關聯,因為 Java 元素並未直接更新 。

Java 元素模型的精細程度不超過方法和欄位。編譯器所使用的抽象語法樹狀結構不是可用的 API, 所以 JDT 用來將原始碼剖析成程式性結構的技術目前尚無 API 的用法。

回應 Java 元素中的變更

如果您的外掛程式需要知道 Java 元素在形成之後的變動,您可將一個 Java IElementChangedListener 登錄到 JavaCore

   JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());

您可以使用 addElementChangedListener(IElementChangedListener, int) 更專注的指定您有興趣的事件類型。

比方說,如果您只對建置器執行之前的接聽事件有興趣:

   JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.PRE_AUTO_BUILD);

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 元素變更事件:

    // 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);

下列程式碼片段則將觸發 1 個 Java 元素變更事件:

    // 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);

Copyright IBM Corporation and others 2000, 2003. All Rights Reserved.