プラグインは JDT API を使用して、クラスまたはインターフェースを作成したり、既存の型にメソッドを追加したり、 あるいは型のメソッドを変更したりすることができます。
Java オブジェクトを最も簡単に変更するには、Java エレメント API を使用します。 生の Java エレメントのソース・コード自体を操作する場合は、さらに一般的な手法を使用できます。
コンパイル単位をプログラムとして生成するには、 IPackageFragment.createCompilationUnit を使用すると容易に行うことができます。 コンパイル単位の名前と内容を指定します。 コンパイル単位はパッケージ内に作成され、新しい ICompilationUnit が戻されます。
パッケージ・ディレクトリーに対応する該当フォルダーに「.java」という拡張子を持つファイル・リソースを作成することにより、 コンパイル単位を包括的に作成することができます。 汎用リソース API は、Java ツール作成の裏側で行われので、汎用リソース変更のリスナーが通知を受けるまで更新は行われず、 JDT リスナーは、新規コンパイル単位を使用して Java モデルを更新します。
Java エレメント API を使用すると、Java ソースを最も簡単に変更することができます。
例えば、コンパイル単位から型を照会することができます。 IType を準備すると、 createField、 createInitializer、 createMethod、 または createType のようなプロトコルを使用して、ソース・コード・メンバーを型に追加することができます。 ソース・コードおよびメンバーのロケーションに関する情報は、これらのメソッドで提供されます。
ISourceManipulation インターフェースは、Java エレメント操作の共通ソースを定義します。 これには、型のメンバーの名前変更、移動、コピー、または削除が含まれます。
コンパイル単位を操作することにより、コードを変更する (および IFile を変更する) か、作業用コピーと呼ばれるコンパイル単位のメモリー内コピーを変更することができます。
作業用コピーをコンパイル単位から取得するには、 getWorkingCopy メソッドを使用します。 (Java モデルにコンパイル単位が存在しなくても、作業用コピーを作成できます。) 作成した作業用コピーが不要になったときには、作成者が責任をもって destroy メソッドを使用してそのコピーを破棄します。
作業用コピーはメモリー内バッファーをコピーします。 getWorkingCopy() メソッドでは、デフォルト・バッファーを作成しますが、 クライアントは、独自のバッファー・インプリメンテーションを getWorkingCopy(IProgressMonitor, IBufferFactory, IProblemRequestor) メソッドを使用して準備することができます。 クライアントはこのバッファーのテキストを直接操作することができます。 その場合、作業用コピーとバッファーをその時々で、同期化するために、 reconcile() メソッドまたは reconcile(boolean,IProgressMonitor) メソッドを使用する必要があります。
作業用コピーを、元のコンパイル単位を置き換えで、最終的にディスクに保管するには、 commit メソッドが使用可能です。
例えば、次のコードの断片では、カスタム・バッファー・ファクトリーを使用して、 コンパイル単位に作業用コピーを作成します。この断片は、バッファーを変更し、 変更を調整し、変更をディスクにコミットして、最後に作業用コピーを破棄します。
// 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 メソッドを使用できます。 共用作業用コピーでは、元のコンパイル単位およびバッファー・ファクトリーにキーが付けられます。
次の例では、クライアント 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;
CompilationUnit を始めから作成するために、 AST 上のファクトリー・メソッドを使用することができます。 これらのメソッド名は new... で始まります。 次の例では、HelloWorld クラスを作成します。
最初の断片は生成された次の出力です。
package example;
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 からでは行えないソース・コード変更もあります。 ソース・コードをより一般的な方法 (例えば、既存のエレメントのソース・コードを変更する) で編集するには、 コンパイル単位の生のソース・コードと Java DOM を使用します。
これらの手法には、以下のものがあります。
// get the source for a compilation unit String contents = myCompilationUnit.getBuffer().getContents(); // Create an editable JDOM myJDOM = new DOMFactory(); myDOMCompilationUnit = myJDOM.createCompilationUnit(contents, "MyClass"); // Navigate and edit the compilation unit structure using // JDOM node protocol. ... // Once modififications have been made to all of the nodes // get the source back from the compilation unit DOM node. String newContents = myDOMCompilationUnit.getContents(); // Set this code back into the compilation unit element myCompilationUnit.getBuffer().setContents(newContents); // Save the buffer to the file. myCompilationUnit.save(null, false);
Java エレメントは直接更新されないため、 この手法を使用すると問題マーカーが誤った行番号に関連付けられる可能性があります。
Java エレメント・モデルは、メソッドおよびフィールド以上に細分化はされません。 コンパイラーが使用する要約構文ツリーは、API として使用できないので、 ソースをプログラムの構造に構文解析するために JDT が使用する技法は、現在 API として使用することはできません。
Java エレメントに変更が加えられた後に、プラグインにそれを認識させる必要がある場合には、 Java の「IElementChangedListener」を 「JavaCore」に登録することができます。
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
addElementChangedListener(IElementChangedListener, int) の使用に関心があるイベントの型を、さらに特定して指定することができます。
例えば、ビルダーを稼動する前に、イベントを listen することのみに関心がある場合は、以下のようにします。
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.PRE_AUTO_BUILD);
JavaCore でサポートされるイベントは 3 種類あります。
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 は、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);