EclipseのASTParserを試す

コード生成のためにパーサーをどう使うか

コードを操作するアプリケーションを書いたことのある人であれば、特に言語がJava™のように高度なものである場合は、それが複雑になりがちなことを知っているでしょう。ところが、既にEclipseを使っている人には朗報があります。JDT(Java Development Tooling)とASTParserが利用できるのです。この記事では、これを紹介します。

Manoel Marques (manoel@themsslink.com), Senior Consultant, The Missing Link, Inc.

Author photoManoel Marquesはこの15年間、ブラジルとアメリカにおいて、ソフトウェア開発者として、また技術コンサルタントとして、ビジネスから研究に至るまでの様々なプロジェクトに携わってきています。ブラジル、リオデジャネイロのPontificia Universidade Catolica PUC大学にて、コンピューター・サイエンスで修士号を取得しています。



2005年 4月 12日

Eclipse JDTはどのように役立つのか

Eclipse JDTが提供するAPIを利用すると、Javaのソース・コードを操作し、エラーを検出し、コンパイルを実行し、プログラムを起動することができます。この記事では、ASTParserを使って全く初めからJavaクラスを作る方法を解説します。また、JDTサービスが、生成されたコードをコンパイルするためにも使えることも説明します。

EclipseのJDTは、よく知られたXML DOMと同じ考え方の、独自のDOM(Document Object Model)、AST(Abstract Syntax Tree)を持っています。

Eclipse V3.0.2はJLS2(Java Language Specification, Second Edition)をサポートしており、J2SE 1.4までのバージョンのJavaで書かれたプログラムを正しく構文解析することができます。JLS3サポートに向けた作業も行われているため、次のメジャーEclipseリリースでは、新しいJ2SE 1.5構成体を使って書かれたプログラムも構文解析できるはずです。


誰でも利用できる無料コード

この記事では、下記の2つのサンプル・アプリケーションを提供しています。どちらも、ASTExplorerというEclipseプロジェクトに含まれています。

  • ASTMain
  • ASTExplorer

ASTMainはJavaクラスを生成し、コンパイルし、そしてそのmain() メソッドを実行します。このメソッドは、ボタンを持つSWT Shellウィジットを作ります。

ASTExplorerは、与えられたJavaクラスに対するAST階層構造を構文解析し、表示します。これには3つのペインが含まれています。AST階層構造を示すSWT Treeビューを持つペイン、オリジナルのソース・コードを示すペイン、そして、パーサー・エラーを示すペインの3つです。

図1はASTExplorerが実際に動作しているところを示しています。あるノードを選択すると、対応するソース・コード部分が青でハイライトされることに注意してください。構文解析エラーは、赤でハイライトされます。

図1. ASTExplorerが実際に動作しているところ
図1. ASTExplorerが実際に動作しているところ

このサンプルは、Eclipse V3.0.1とV3.0.2で、またWindows® XP Professional SP2とSun J2SDK 1.4.2.05でテストされています。現在のプロジェクトのクラスパスには、Eclipse V3.0.2に対するエントリーがあります。Eclipse V3.0.1で実行する必要がある場合には、正しいプラグインを指すように、単純にクラスパスを変更するだけです。

この先を読む前に、このサンプル・アプリケーションをダウンロードするようにお勧めします。ここでのキー・ワードは、「試すこと(exploration)」です。記事を読みながらサンプルを実行した方が、学習効果が高くなるのです。


ASTParser と ASTNodes

AST階層構造の最上位には、ASTNodeがあります。どのJava構成体も、これによって表現されます。ノード名の大部分は、CommentやCastExpressionなど、自明だと思います。これらは、newBlock()newBreakStatement()などのような、ASTクラスのメソッドを使って作ります。Javaクラスは、Compilation Unitノードによって表現されます。リスト1は、これがどのように作られるかを示しています。

リスト1. Compilation Unitを作る
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource("".toCharArray());
CompilationUnit unit = (CompilationUnit) parser.createAST(null); 
unit.recordModifications();
AST ast = unit.getAST();

JLS2に関して、ASTParser がどのように構成されているかに注意してください。ASTParserは次に、空の配列で初期化されます。これをしないと、Compilation Unitにアクセスしようとすると例外を受け取ることになります。

こうしたステップは、既存のコードを構文解析する場合も同じです。この場合では、長い構文解析の間にフィードバックが得られるように、org.eclipse.core.runtime.IProgressMonitorのインスタンスをcreateAST() メソッドに渡す必要があるかも知れません。この使い方については、後ほど説明します。

recordModifications() へのコールで、コード変化の監視が始まります。このメソッドを呼ぶことによって、後からノード修正を検索してソースにアクセスできるようになるので、このメソッドを呼ぶことは非常に重要です。

最後に、Compilation UnitからAST所有者に対してアクセスします。後に続くノード生成には、すべてAST所有者が使われます。AST Treeの全ノードは、同じ所有者に属します。この所有者が作ったものでないノードをツリーに追加するためには、それらを最初にインポートする必要があります。その時点で、Javaクラスを作る準備ができたことになります。リスト2は、パッケージの生成を示しています。

リスト2. パッケージを作る
PackageDeclaration packageDeclaration = ast.newPackageDeclaration();
unit.setPackage(packageDeclaration);
packageDeclaration.setName(ast.newSimpleName("astexplorer"));

ノード・メソッドの幾つかは、Nameノードを使います。NameノードはSimpleNameまたはQualifiedNameであり、これらはSimpleName のグループです。例えばQualifiedNameの外部表現は、org.eclipse.swt.widgetsです。従って基本的に、ドットがある場合には、必ずQualifiedNameを使います。メソッドast.newName()を使うと、ストリング配列を受け入れることによってNameノードが作れるようになります。このコード・サンプルでは、ドットを持つストリングを構文解析し、このストリング配列を作る便利なメソッドを提供しています。

主なノード・グループは6つあります。BodyDeclarationとComment、Expression、StatementTypeそしてVariableDeclarationです。BodyDeclarationsは、クラス内部に入る、任意の宣言です。例えば、宣言private Point minimumSize; を作るには、次のようにします。

リスト3. VariableDeclarationを作る
VariableDeclarationFragment vdf = ast.newVariableDeclarationFragment();
vdf.setName(ast.newSimpleName("minimumSize"));
FieldDeclaration fd = ast.newFieldDeclaration(vdf);
fd.setModifiers(Modifier.PRIVATE);
fd.setType(ast.newSimpleType(ast.newSimpleName("Point")));

FieldDeclarationが、VariableDeclarationFragment からどのように作られるかに注意してください。ASTプログラミングというのは、異なるノードを組み合わせることなのです。XMLのDOMにある、appendChild()insertBefore() のようなメソッドは使いません。ASTプログラミングでは、ノード・タイプによって、作り方や初期化の仕方がそれぞれ異なるのです。

今ちょうど、VariableDeclarationのタイプの例、VariableDeclarationFragmentを見ました。もう一方のタイプ、SingleVariableDeclarationは主に、パラメーター・リストに使われます。例えばリスト4は、ControlAdapterImpl(Point size)の中で、どのようにパラメーター、sizeを作るかを示しています。

リスト4. メソッドParameterを作る
SingleVariableDeclaration variableDeclaration = ast.newSingleVariableDeclaration();
variableDeclaration.setModifiers(Modifier.NONE);
variableDeclaration.setType(ast.newSimpleType(ast.newSimpleName("Point")));
variableDeclaration.setName(ast.newSimpleName("size"));
methodConstructor.parameters().add(variableDeclaration);

Commentノードには3つのタイプがあります。BlockCommentとJavadoc、そしてLineCommentです。

AST Treeがサポートしているのは、Javadocノードの生成と挿入だけです。AST Treeでは、BlockComment ノードとLineCommentノードの位置を厳密に決めるのは問題であるとしているため、これらを見るのは、既存のソースを構文解析する場合くらいのものでしょう。リスト5は、Javadocノードの生成の例です。

リスト5. Javadocノードを作る
Javadoc jc = ast.newJavadoc();
TagElement tag = ast.newTagElement();
TextElement te = ast.newTextElement();
tag.fragments().add(te);
te.setText("Sample SWT Composite class created using the ASTParser");
jc.tags().add(tag);
tag = ast.newTagElement();
tag.setTagName(TagElement.TAG_AUTHOR);
tag.fragments().add(ast.newSimpleName("Manoel Marques"));
jc.tags().add(tag);
classType.setJavadoc(jc);

ExpressionノードとStatementノードは、最も頻繁に使われるノード・タイプです。サンプル・コードには、この作り方の例が幾つか入っています。GridLayout gridLayout = new GridLayout() のような単純なステートメントは、次のように作ることができます。

リスト6. ステートメントを作る
VariableDeclarationFragment vdf = ast.newVariableDeclarationFragment();
vdf.setName(ast.newSimpleName("gridLayout"));
VariableDeclarationStatement vds = ast.newVariableDeclarationStatement(vdf);
vds.setType(ast.newSimpleType(ast.newSimpleName("GridLayout")));
ClassInstanceCreation cc = ast.newClassInstanceCreation();
cc.setName(ast.newSimpleName("GridLayout"));	
vdf.setInitializer(cc);
constructorBlock.statements().add(vds);

ノードがどのように構成されているかに注意してください。ステートメント全体は、タイプGridLayoutのVariableDeclarationStatementです。これはVariableDeclarationFragmentを含んでおり、VariableDeclarationFragmentはClassInstanceCreationを含んでいます。

同じステートメントは、リスト7で示すように、代入式(Assignment expression)を使って作ることもできます。

リスト7. 同じステートメントを作る、別の方法
Assignment a = ast.newAssignment();
a.setOperator(Assignment.Operator.ASSIGN);

VariableDeclarationFragment vdf = ast.newVariableDeclarationFragment();
vdf.setName(ast.newSimpleName("gridLayout"));
VariableDeclarationExpression vde = ast.newVariableDeclarationExpression(vdf);
vde.setType(ast.newSimpleType(ast.newSimpleName("GridLayout")));
a.setLeftHandSide(vde);

ClassInstanceCreation cc = ast.newClassInstanceCreation();
cc.setName(ast.newSimpleName("GridLayout"));	
a.setRightHandSide(cc);
constructorBlock.statements().add(ast.newExpressionStatement(a));

これは、左側にVariableDeclarationFragmentを、右側にClassInstanceCreationを含む、VariableDeclarationExpressionを持った代入式と考えることもできます。代入式は、ステートメント・リストに追加される前に、メソッドnewExpressionStatement() を持つStatementでラップされていることに注意してください。

どちらの方法を使っても、同じソース・コードができますが、最初の方法を使うべきです。既存のコードを構文解析してみれば、作られたコードでは最初の方法が使われていることが分かるでしょう。ですから、ASTExplorerサンプルを使うことが重要なのです。この方法を使えば、特定なコード断片のためにパーサーが作ったノードを視覚化でき、同じようにして自分のノードも作ることができるのです。

ASTMainクラス・サンプルを見ると、幾つかの状況に合わせて様々なノードを作るにはどうすべきか、よく分かると思います。私は、内部クラスやトライ・ブロック、配列パラメーター等々の、面倒な構成体も全て含めるようにしました。私が自分で問題を経験したことのある領域や、皆さんが助けを必要としそうな領域を網羅するようにしたつもりです。

実際のソース・コードを取得する

Compilation Unitがあれば、実際のソース・コードを取得するのは簡単です。

recordModifications()を呼ぶことによって、既に仕事の半分は終わっています。後は、単にCompilation Unitにあるメソッド、rewrite()を呼ぶだけです。これはorg.eclipse.jface.text.IDocumentのインスタンスと、フォーマット・オプションのMapをとります。IDocumentインスタンスは、オリジナルのソースを含むことになっています(この場合では、オリジナル・ソースはありません)。そうするとこのメソッドは、Compilation Unitの中での変更と文書テキストをマージし、全ての変更を含んだorg.eclipse.jface.text.edits.TextEditのインスタンスを返します。

フォーマット・オプションを使うと、角括弧や段下げなどのオプションを規定できます。こうしたオプションのリストは、クラス、org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstantsの中にあります。

TextEditインスタンスを取得できると、それを使って、何が変更されたかを調べることができます。この場合では、単にオリジナル文書に対して変更を適用します。

これで、全てのコードは文書の中にあり、抽出できるようになりました。このプロセスはリスト8のようになります。

リスト8. Compilation Unitの内容にアクセスする
public char[] getContents() {
char[] contents = null;
try {
Document doc = new Document();
TextEdit edits = unit.rewrite(doc,null);
edits.apply(doc);
String sourceCode = doc.get();
if (sourceCode != null) contents = sourceCode.toCharArray(); }
catch (BadLocationException e) {
throw new RuntimeException(e);
}
return contents;
}

生成されたコードをコンパイルする

Eclipse IDEは、ワークスペースにあるプロジェクトで動作します。プロジェクト全体を構築し、JDTを使って依存関係をチェックし、全てのクラスをコンパイルします。これはEclipseのヘルプによく説明されていますが、煎じ詰めると、次のようになります。

リスト9. Eclipseプロジェクトを構築する
IProject myProject;
IProgressMonitor myProgressMonitor;
myProject.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, myProgressMonitor);

これについてはEclipseのヘルプでよく説明されているため、ここでは時間をかけないことにします。JDTでは、クラス、org.eclipse.jdt.internal.compiler.Compilerを使った別の方法が提供されており、こちらの方が私達の目的には、より便利です。これは実は、非常に単純なのです。コンパイラー・オブジェクトをインスタンス化し、それに対してメソッドcompile()を呼べばよいのです。

リスト10. Compilation Unitをコンパイルする
Compiler compiler = new Compiler(new NameEnvironmentImpl(unit),
DefaultErrorHandlingPolicies.proceedWithAllProblems(),
settings,requestor,new DefaultProblemFactory(Locale.getDefault()));
compiler.compile(new ICompilationUnit[] { unit });

まず、コンストラクター・パラメーターを説明しましょう。下記のものが必要になります。

org.eclipse.jdt.internal.compiler.env.INameEnvironment
コンパイラーを、外部環境と結合します。簡単に言えば、クラスパスを表します。コンパイラーはこれを使って、遭遇するタイプに関する情報を尋ねます。
org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy
コンパイラーに対して、エラーに遭遇した場合にどうすべきかを伝えます。私は、コンパイラーは停止するまで可能な限り先に進んで欲しいので、事前定義されたインスタンス、DefaultErrorHandlingPolicies.proceedWithAllProblems()を使っています。
Map Settings
org.eclipse.jdt.internal.compiler.impl.CompilerOptionsの中にあるコンパイラー設定です。これを使うと、行番号を生成する必要があるか、推奨されないメソッド警告が必要かどうか、などを規定することができます。
org.eclipse.jdt.internal.compiler.ICompilerRequestor
コンパイル結果と、コンパイル・プロセス中に遭遇した全てのエラーを受け取ります。
org.eclipse.jdt.internal.compiler.IProblemFactory
org.eclipse.jdt.core.compiler.IProblemのインスタンスを作る責任を持つファクトリーです。特別なタイプの問題処理や、エラー・メッセージ用に他の言語をサポートする場合には、これを実装すると便利です。このサンプルでは、標準実装、DefaultProblemFactory(Locale.getDefault())を使っています。

最後に、コンパイルするためには、org.eclipse.jdt.internal.compiler.env.ICompilationUnitの配列が必要です。このインターフェースと、org.eclipse.jdt.core.ICompilationUnitを混同しないでください。残念ながら、どちらも同じ名前ですが、後者が便利なのは、クラスがEclipseのJavaプロジェクトの一部である場合のみです。

org.eclipse.jdt.internal.compiler.env.ICompilationUnitの実装は容易です。これは、既に作った、CompilationUnitノードに対応します。リスト11は、単純な実装を示しています。

リスト11. ICompilationUnitの実装
static private class CompilationUnitImpl implements ICompilationUnit {
private CompilationUnit unit;

CompilationUnitImpl(CompilationUnit unit) {
this.unit = unit;
}
public char[] getContents() {
char[] contents = null;
try {
Document doc = new Document();
TextEdit edits = unit.rewrite(doc,null);
edits.apply(doc);
String sourceCode = doc.get();
if (sourceCode != null) 
contents = sourceCode.toCharArray(); 
}
catch (BadLocationException e) {
throw new RuntimeException(e);
}
return contents;
}
public char[] getMainTypeName() {
TypeDeclaration classType = (TypeDeclaration) unit.types().get(0); 
return classType.getName().getFullyQualifiedName().toCharArray(); 
}
public char[][] getPackageName() {
String[] names = 
getSimpleNames(this.unit.getPackage().getName().getFullyQualifiedName()); 
char[][] packages = new char[names.length][];
for (int i=0;i < names.length; ++i)
packages[i] = names[i].toCharArray();

return packages;
}
public char[] getFileName() {
TypeDeclaration classType = (TypeDeclaration) unit.types().get(0); 
String name = classType.getName().getFullyQualifiedName() + ".java"; 
return name.toCharArray();  
}
}

コンパイル・エラーをチェックする

コンパイルの後、最初にすべきことは、ICompilerRequestor実装から、可能性のある全エラーを取得することです。こうしたエラーは、単なる警告の場合もあれば、致命的なエラーの場合もあります。簡単なチェックを下記に示します。

リスト12. コンパイル・エラーを処理する
List problems = requestor.getProblems();
boolean error = false;
for (Iterator it = problems.iterator(); it.hasNext();) {
IProblem problem = (IProblem)it.next();
StringBuffer buffer = new StringBuffer();
buffer.append(problem.getMessage());
buffer.append(" line: ");
buffer.append(problem.getSourceLineNumber());
String msg = buffer.toString(); if(problem.isError()) {
error = true; msg = "Error:\n" + msg;
}	
else if(problem.isWarning())
msg = "Warning:\n" + msg;
System.out.println(msg);  
}

コンパイルしたアプリケーションを実行する

全てが順調に行けば、今度はクラスをインスタンス化し、そのメイン・メソッドを実行する番です。これは、ICompilerRequestor実装が返すバイトコードに対するリフレクションを使って、容易に行うことができます。

リスト13. コンパイルしたアプリケーションを実行する

リスティングを見るにはここをクリック

リスト13. コンパイルしたアプリケーションを実行する

try {
ClassLoader loader = new CustomClassLoader(getClass().getClassLoader(),
requestor.getResults());
String className = CharOperation.toString(unit.getPackageName()) + "." + new String(unit.getMainTypeName());
Class clazz = loader.loadClass(className);
Method m = clazz.getMethod("main",new Class[] {String[].class});
m.invoke(clazz,new Object[] { new String[0] });
}
catch (Exception e) {
e.printStackTrace();
}

カスタムのクラス・ローダーから、どのようにクラスにアクセスしているかに注意してください。要求された時に、コンパイルされたバイトコードをロードするのです。この例をリスト14に示します。

リスト14. カスタムのクラス・ローダー
static private class CustomClassLoader extends ClassLoader {
private Map classMap;
CustomClassLoader(ClassLoader parent,List classesList) {
this.classMap = new HashMap();
for (int i = 0; i < classesList.size(); i++) {
ClassFile classFile = (ClassFile)classesList.get(i);
String className = CharOperation.toString(classFile.getCompoundName());
this.classMap.put(className,classFile.getBytes());
}
}
public Class findClass(String name) throws ClassNotFoundException {
byte[] bytes = (byte[]) this.classMap.get(name);
if (bytes != null)
return defineClass(name, bytes, 0, bytes.length);
return super.findClass(name);
}
}

これは非常に基本的なコンパイラーです。INameEnvironment実装は単純であり、クラスの依存関係は全て、既に現在のクラス・ローダーにロードされていると想定するのです。現実の実装では、コンパイルのためだけに提供される一部のクラスパスを検索するための、別のカスタム・クラス・ローダーが必要かも知れません。

また、一部の情報、特にICompilationUnit実装から返される情報は、キャッシュする必要があるかも知れません。例えば、ソース・コードを取得するプロセスは時間がかかるため、キャッシュすべきでしょう。


既存のコードを構文解析する

構文解析のために必要なステップを復習してみましょう。リスト15を見てください。

リスト15. Javaクラスを構文解析する
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setSource(sourceString.toCharArray());
CompilationUnit node = (CompilationUnit) parser.createAST(null);

この構文解析の結果はCompilation Unitであることは分かっているので、パーサーを ASTParser.K_COMPILATION_UNITで初期化します。または、 K_CLASS_BODY_DECKARATION ,K_EXPRESSIONK_STATEMENTSを使うこともできます。あるいは、次のようにすることもできます。

リスト16. Javaステートメントを構文解析する
parser.setKind(ASTParser.K_STATEMENTS);
parser.setSource(sourceString.toCharArray());
Block block =  (Block) parser.createAST(null);

後から既存のブロックに挿入されるステートメントが山ほどある場合には、これが便利です。ただし、挿入する前には、構文解析したノード・ブロックをインポートすることを忘れないでください。例えば、block = (Block)ASTNode.copySubtree(unit.getAST(), block);のようにします。

createAST()に対するパラメーターは、org.eclipse.core.runtime.IProgressMonitorです。これは、ノードを作る時には必要ありませんが、構文解析する時には重要になります。これを使うと、外部の観察者がタスクの進行状況を追うことができ、必要な場合には中止できるようになります。構文解析は、UIスレッドがIProgressMonitorから通知を受信する間に、別のスレッドで行うこともできます。

読み取り専用のツリー操作は、そのツリーを修正するようなスレッドが何も無ければ、スレッド・セーフです。もし他のスレッドがノードを変更できる場合であれば、ツリー、(synchronize (node.getAST()) {...})を所有するASTオブジェクトを同期させる方が良いでしょう。

JFacesライブラリーには、IProgressMonitorをorg.eclipse.jface.dialogs.ProgressMonitorDialogにカプセル化する、便利なダイアログが用意されています。これは、リスト17に示すように使います。

リスト17. IProgressMonitorで構文解析する
ProgressMonitorDialog dialog = new ProgressMonitorDialog(getShell());
dialog.run(true, true, new IRunnableWithProgress() {
public void run(final IProgressMonitor monitor) 
throws InvocationTargetException {
try {	
ASTParser parser = ASTParser.newParser(AST.JLS2);
if (monitor.isCanceled()) return;
parser.setKind(ASTParser.K_COMPILATION_UNIT); if (monitor.isCanceled()) return;
final String text = buffer.toString();
parser.setSource(text.toCharArray());
if (monitor.isCanceled()) return;
final CompilationUnit node = 
(CompilationUnit) parser.createAST(monitor);
if (monitor.isCanceled()) return;
getDisplay().syncExec(new Runnable() {
public void run() {
// update the UI with the result of parsing ...
}
}); 					
}
catch (IOException e) {
throw new InvocationTargetException(e);
}
}
});

このダイアログ実行メソッドに対する最初の2つのブール・パラメーターは、IRunnableWithProgressインスタンス実行メソッドは別スレッドにあること、そしてそれが中止できることを示しています。こうすれば、中止ボタンを持ったダイアログが表示される一方で、構文解析が別スレッドで実行されます。もしユーザーがボタンを押すと、ダイアログはIProgressMonitorインスタンス・メソッドsetCanceled() を、真に設定します。このインスタンスもパーサーに渡されているため、パーサー動作も停止します。

構文解析が終了したら、構文解析を行ったスレッドではなく、UI自身のUIスレッドを使って、構文解析の結果でUIを更新する必要があります。これを行うためには、UIスレッドにある実行可能なインスタンスでコードを実行する、org.eclipse.swt.widgets.Display.syncExecメソッドを使います。

ツリーをウォークする

ASTNodeでは、訪問者パターン(visitor pattern、参考文献を参照)を使って、ノード・ツリーをウォークすることができます。org.eclipse.jdt.core.dom.ASTVisitorから派生したクラスを作り、そのインスタンスを、ノードの、メソッドaccept()に渡すのです。このメソッドが呼ばれると、ツリーの各ノードのうち、現在のノードから先のノードが「訪問」を受けることになります。各ノードに対して、次のメソッドが呼び出されます。

  1. preVisit(ASTNode node)
  2. boolean visit(node)
  3. endVisit(node)
  4. postVisit(ASTNode node)

各ノード・タイプに対して、visit()endVisit()という、一つのメソッドがあります。パラメーター・ノードのタイプは、訪問を受けているノードに対応します。visit() メソッドから偽を返すと、このノードの子は、訪問を受けません。

コメント・ノードには親が無いため、AST Treeには現れません。メソッドgetParent() は、ヌルを返します。Compilation Unitにあるメソッド、getCommentList() を呼ぶことによって、コメント・ノードにアクセスすることは可能です。コメント・ノードを表示する必要がある場合には、このメソッドを呼び、各コメント・ノードを個別に訪問します。

このサンプルの、クラスASTExplorerVisitorには、コメント付きコードのブロックがpreVisit() メソッドの中に含まれています。これらのコメントを外さないと、全てのコメント・ノードがCompilation Unitの子として表示されます。

パーサー・エラーは、Compilation Unitクラス・インスタンスの中で返されます。メソッドgetProblems() は、IProblemインスタンスの配列を返します。これは、コンパイル・エラーで使われたIProblemクラスと同じです。

ASTParserはコンパイラーではありません。これは重要ですので注意してください。ASTParserがエラーのフラグを立てるのは、AST Treeの整合性に影響するような何かがソース・ファイルの中にある場合のみです。

例えば、classではなく、classsとタイプすると、TypeDeclarationノードの生成に影響し、エラーとなります。一方、private Stringgg str;とタイプしても、Stringggというクラスはどこかに存在するかも知れないので、この場合は有効なのです。これをエラーとしてフラグを立てられるのは、コンパイラーだけです。ツリーが有効だからといって、コンパイルが有効とは限らないことに注意してください。


まとめ

これで、私がASTParserを使う中で経験した「なるほど!」は、全て網羅しました。これで皆さんも、プロジェクトの中にJDTサービスを追加することができます。これは非常に強力なので、皆さんが新たに何かを発明する必要はありません。構文解析を楽しんでください!


ダウンロード

内容ファイル名サイズ
ASTExplorerとサンプル・コードos-astexplorer.zip44KB

参考文献

  • Eclipseには、ドキュメンテーションやソース・コード、そして最新のビルドなどがあります。
  • Eclipse Platform入門(developerWorks, 2002年11月)は、Eclipseとプラグインのインストール方法の詳細を含めて、Eclipseの歴史と概要を解説しています。
  • Eclipse In Action: A Guide for Java Developers(2003年Independent Publishers Group刊)は、Eclipseを使うJava開発者にとって必読です。
  • Javaについてさらに学ぶために、developerWorksのJavaゾーンを見てください。技術資料やハウツー記事、教育資料、ダウンロード、製品情報などが豊富に用意されています。
  • developerWorksのEclipse resourcesには、Eclipse開発者のための資料が豊富に用意されています。
  • Erich GammaとRichard Helm、Ralph Johnson、そしてJohn Vlissidesの共著による、Design Patterns -- Elements of Reusable Object-Oriented Software(1997年Addison Wesley刊)には、訪問者パターンに関する詳細な情報が含まれています。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Java technology
ArticleID=236387
ArticleTitle=EclipseのASTParserを試す
publish-date=04122005