プラグインは JDT API を使用して、クラスまたはインターフェースを作成したり、既存の型にメソッドを追加したり、 あるいは型のメソッドを変更したりすることができます。
Java オブジェクトを最も簡単に変更するには、Java エレメント API を使用します。 生の Java エレメントのソース・コード自体を操作する場合は、さらに一般的な手法を使用できます。
コンパイル単位をプログラマチックに生成する最も簡単な方法は、 IPackageFragment.createCompilationUnit を使用することです。 コンパイル単位の名前と内容を指定します。 コンパイル単位はパッケージ内に作成され、新しい ICompilationUnit が戻されます。
パッケージ・ディレクトリーに対応する該当フォルダーに「.java」という拡張子を持つファイル・リソースを作成することにより、 コンパイル単位を包括的に作成することができます。 汎用リソース API は、Java ツール作成の裏側で使用されるので、汎用リソース変更のリスナーが通知を受けるまで Java モデルの更新は行われず、 JDT リスナーは、新規コンパイル単位を使用して Java モデルを更新します。
Java エレメント API を使用すると、Java ソースを最も簡単に変更することができます。
例えば、コンパイル単位から型を照会することができます。 IType を準備すると、 createField、 createInitializer、 createMethod、 または createType のようなプロトコルを使用して、ソース・コード・メンバーを型に追加することができます。 ソース・コードおよびメンバーのロケーションに関する情報は、これらのメソッドで提供されます。
ISourceManipulation インターフェースは、Java エレメント操作の共通ソースを定義します。 これには、型のメンバーの名前変更、移動、コピー、または削除を行うメソッドが含まれます。
コンパイル単位を操作することにより、コードを変更する (および IFile を変更する) か、作業用コピーと呼ばれるコンパイル単位のメモリー内コピーを変更することができます。
作業用コピーをコンパイル単位から取得するには、 getWorkingCopy メソッドを使用します。(Java モデルにコンパイル単位が存在しなくても、作業用コピーを作成できることに注意してください。) 作成した作業用コピーが不要になったときには、作成者が責任を持って discardWorkingCopy メソッドを使用してこのコピーを破棄します。
作業用コピーはメモリー内バッファーを変更します。 getWorkingCopy() メソッドはデフォルト・バッファーを作成しますが、 クライアントが、 getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor) メソッドを使用して独自のバッファー実装を準備することもできます。 クライアントはこのバッファーのテキストを直接操作することができます。 その場合、作業用コピーとバッファーをその時々で同期化しなければなりません。 そのためには、reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor) メソッドを使用します。
最終的に作業用コピーを (元のコンパイル単位に置き換えて) ディスクに保管するには、 commitWorkingCopy メソッドを使用します。
例えば、次のコードの断片では、カスタム作業用コピー所有者を使用してコンパイル単位に作業用コピーを作成します。 この断片は、バッファーを変更し、変更点を調整し、変更をディスクにコミットして、最後に作業用コピーを破棄します。
// Get original compilation unit ICompilationUnit originalUnit = ...; // Get working copy owner WorkingCopyOwner owner = ...; // Create working copy ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // Modify buffer and reconcile IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // Commit changes workingCopy.commitWorkingCopy(false, null); // Destroy working copy workingCopy.discardWorkingCopy();
作業用コピーは、作業用コピーの所有者を使用して、クライアント間でも共用可能です。 作業用コピーを後で検索するには、 findWorkingCopy メソッドを使用します。共用作業用コピーでは、元のコンパイル単位および作業用コピーの所有者にキーが付けられます。
次の例では、クライアント 1 が共用作業用コピーを作成し、クライアント 2 がこの作業用コピーを検索し、 クライアント 1 が作業用コピーを破棄し、クライアント 2 がその共用作業用コピーを検索しようとしますが、 それが存在していないことが示されます。
// Client 1 & 2: Get original compilation unit ICompilationUnit originalUnit = ...; // Client 1 & 2: Get working copy owner WorkingCopyOwner owner = ...; // Client 1: Create shared working copy ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // Client 2: Retrieve shared working copy ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // This is the same working copy assert workingCopyForClient1 == workingCopyForClient2; // Client 1: Discard shared working copy workingCopyForClient1.discardWorkingCopy(); // Client 2: Attempt to retrieve shared working copy and find out it's null workingCopyForClient2 = originalUnit.findWorkingCopy(owner); assert workingCopyForClient2 == null;
AST 上のファクトリー・メソッドを使用して、最初から CompilationUnit を作成することが可能です。 これらのメソッド名は 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 からでは行えないソース・コード変更もあります。 ソース・コードをより一般的な方法 (例えば、既存のエレメントのソース・コードを変更するなど) で編集するには、コンパイル単位の未加工のソース・コードと DOM/AST の再書き込み API を使用します。
DOM/AST の再書き込みを実行するための API は、2 つのセットがあります。 記述再書き込みと変更再書き込みです。
記述 API は AST を変更しませんが、ASTRewrite
API を使用して変更の記述を生成します。
AST の再書き込み機能は、ノードに対する変更の記述を集めて、これらの記述をテキスト編集に変換します。
さらに、それらのテキスト編集をオリジナル・ソースに適用できます。
// creation of a Document
ICompilationUnit cu = ... ; // content is "public class X {¥n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// creation of DOM/AST from a ICompilationUnit
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// creation of ASTRewrite
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// description of the change
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// computation of the text edits
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// computation of the new source code
edits.apply(document);
String newSource = document.get();
// update of the compilation unit
cu.getBuffer().setContents(newSource);
変更 API を使用すると、AST を直接変更することができます。
// creation of a Document ICompilationUnit cu = ... ; // content is "public class X {¥n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // creation of DOM/AST from a ICompilationUnit ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // start record of the modifications astRoot.recordModifications(); // modify the AST TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // computation of the text edits TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // computation of the new source code edits.apply(document); String newSource = document.get(); // update of the compilation unit cu.getBuffer().setContents(newSource);
Java エレメントに変更が加えられた後に、プラグインにそれを認識させる必要がある場合には、 Java の「IElementChangedListener」を 「JavaCore」に登録することができます。
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
addElementChangedListener(IElementChangedListener, int) の使用に関心があるイベントの型を、さらに特定して指定することができます。
例えば、調整操作中に、イベントを listen することのみに関心がある場合は、以下のようにします。
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
JavaCore でサポートされるイベントは 2 種類あります。
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);