Eclipseプラグイン実践テクニック(4)

ビルド機能を応用してXML検証機能を作る



NTTデータ先端技術 竹添直樹
NTTデータ 基盤システム事業本部 岡本隆史
2006/12/9


ビルダの機能を応用してSAXパースする

 続いて、XMLファイルのバリデーションを行うビルダに移ります。ビルダを定義するには拡張ポイントorg.eclipse.core.resources.buildersを使用します。また、このビルダはすでに作成したXMLNatureと関連付けますので、ネイチャの定義にビルダとの関連付けを追加します。plugin.xmlは以下のようになります(追加部分を赤字にしてあります)。

リスト4 ビルダの定義
<extension
    id="XMLBuilder"
    name="XMLビルダ"
    point="org.eclipse.core.resources.builders">
    <builder hasNature="true">
        <run
        class="jp.sf.amateras.builder.XMLBuilder"> ... (1)
        </run>
    </builder>
</extension>

<extension
    id="sampleNature"
    name="XMLネイチャ"
    point="org.eclipse.core.resources.natures">
    <runtime>
        <run
        class="jp.sf.amateras.builder.XMLNature">
        </run>
    </runtime>
    <builder
        id="jp.sf.amateras.builder.XMLBuilder">
    </builder>
</extension>

 (1)で指定されているXMLBuilderがビルド処理を行うクラスです。このクラスはorg.eclipse.core.resources.IncrementalProjectBuilderクラスを継承して実装します。

リスト5 XMLBuilder
public class XMLBuilder extends IncrementalProjectBuilder {
    public static final String BUILDER_ID =         "jp.sf.amateras.builder.XMLBuilder";
    protected IProject[] build(int kind, Map args,                         IProgressMonitor monitor) // … (1)
    throws CoreException {
        if (kind == FULL_BUILD) {
            fullBuild(monitor);
        } else {
            IResourceDelta delta = getDelta(getProject());
            if (delta == null) {
                fullBuild(monitor);
            } else {
                incrementalBuild(delta, monitor);
            }
        }
        return null;
    }
    /** フルビルド */
    private void fullBuild(
                        IProgressMonitor monitor){ // … (2)
        try {
            getProject().accept(new XMLResourceVisitor());
        } catch (CoreException e) {
            e.printStackTrace();
        }
    }
    /** インクリメンタル・ビルド */
    private void incrementalBuild(IResourceDelta delta,                         IProgressMonitor monitor){ // … (3)
        try {
            delta.accept(new XMLDeltaVisitor());
        } catch (CoreException e) {
            e.printStackTrace();
        }
    }
}

 ビルドを行う場合、(1)のbuild()メソッドが呼び出されます。ビルドは場合によって、プロジェクト全体のビルドを行うフルビルドと、変更されたファイルのみビルドを行うインクリメンタル・ビルドに分かれます。上記の例では、フルビルドの場合は(2)のfullBuild()メソッド、インクリメンタル・ビルドの場合は(3)のincrementalBuild()メソッドを呼び出しています。

フルビルドの場合はリソースビジタ

 フルビルドの場合は、プロジェクト内のすべてのファイルを処理する必要があります。そこで、リソースビジタを使用してプロジェクト内のすべてのリソースをツリーウォークします。リソースビジタはorg.eclipse.core.resources.IResourceVisitorインターフェイスを実装して作成します。実装しなければならないのはvisit()メソッドだけです。

リスト6 XMLResourceVisitor
public class XMLResourceVisitor implements IResourceVisitor {
    public boolean visit(IResource resource)
    throws CoreException {
        XMLValidator.doValidate(resource);
        return true;
    }
}

 XMLValidator#doValidate()でXMLのバリデーションを行っています。バリデーション処理の実装については、後述します。

インクリメンタル・ビルドの場合はリソースデルタ

 インクリメンタル・ビルドの場合は、変更の

リスト7 XMLDeltaVisitor
public class XMLDeltaVisitor implements IResourceDeltaVisitor {
    public boolean visit(IResourceDelta delta)
    throws CoreException {
        IResource resource = delta.getResource();
        int kind = delta.getKind();
        if(kind == IResourceDelta.ADDED
        || kind == IResourceDelta.CHANGED){
            XMLValidator.doValidate(resource);
        }
        return true;
    }
}

差分を格納したリソースデルタ(IResourceDelta)をツリーウォークします。この場合、ビジタクラスはorg.eclipse.core.resources.IResourceDeltaVisitorインターフェイスを実装します。

 リソースデルタには、プロジェクト内のどのリソースが追加、変更、削除されたのかがツリー状に格納されています。ここでは、追加もしくは変更されたリソースに対してXMLバリデーションを行っています。

バリデーションとリソース・マーカ

 さて、肝心のXMLのバリデーション処理ですが、SAXパーサのエラーハンドラで行うことにします。エラーハンドラは、エラーが発生した際にバリデーション対象のファイルに対してリソース・マーカを付与するよう実装します。リソース・マーカはエディタ左端のルーラや問題ビューなどに表示されます。

編集部注:SAXパーサについて詳しく知りたい読者は、@IT XML&SOA フォーラムの、「SAXによるシンプルなXML文書の操作」や「XMLプログラミングのためのAPI」を参照してください。
図1 リソース・マーカ

 バリデーション処理の実装は以下のとおりです。

リスト8 XMLValidator
public class XMLValidator {
    private static final String MARKER_TYPE =         "jp.sf.amateras.builder.XMLProblem";
    private static SAXParserFactory factory =         SAXParserFactory.newInstance();
    /** バリデーション処理 */
    public static void doValidate(IResource resource){
        // XMLファイルだったらバリデーションを行う
        if(resource instanceof IFile
        && resource.getName().endsWith(".xml")){
            IFile file = (IFile) resource;
            try {
                // まずマーカをすべて削除する
                file.deleteMarkers(IMarker.PROBLEM, false,                 IResource.DEPTH_ZERO);
                // エラーハンドラをセットしてパースを行う
                XMLErrorHandler reporter =
                    new XMLErrorHandler(file);
                SAXParser parser = factory.newSAXParser();
                parser.parse(file.getContents(), reporter);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    /** SAXパーサに設定するエラーハンドラ */
    private static class XMLErrorHandler
    extends DefaultHandler {
        private IFile file;
        public XMLErrorHandler(IFile file) {
            this.file = file;
        }
        private void addMarker(SAXParseException e
                                 , int severity) { // … (1)
            // マーカを作成
            String message = e.getMessage();
            int lineNumber = e.getLineNumber();
            try {
                IMarker marker =
                file.createMarker(IMarker.PROBLEM);
                marker.setAttribute(IMarker.MESSAGE, message);
                marker.setAttribute(IMarker.SEVERITY
                                                , severity);
                if (lineNumber == -1) {
                    lineNumber = 1;
                }
                marker.setAttribute(IMarker.LINE_NUMBER
                                                 , lineNumber);
            } catch (CoreException ex) {
                ex.printStackTrace();
            }
        }
        public void error(SAXParseException exception)
        throws SAXException {
            addMarker(exception, IMarker.SEVERITY_ERROR);
        }
        public void fatalError(SAXParseException exception)
        throws SAXException {
            addMarker(exception, IMarker.SEVERITY_ERROR);
        }
        public void warning(SAXParseException exception)
        throws SAXException {
            addMarker(exception, IMarker.SEVERITY_WARNING);
        }
    }
}

 SAXパーサのエラーハンドラでエラーを捕捉すると、(1)のaddMarker()メソッドでリソース・マーカを作成するようになっています。処理自体は普通にSAXパースしているだけですので、特に難しい部分はないでしょう。

2/3

 INDEX
第4回 ビルド機能を応用してXML検証機能を作る
  Page1
XMLのバリデーションを行うビルダの実装
ネイチャについて
Page2
ビルダの機能を応用してSAXパースする
  Page3
ネイチャをプロジェクトに追加する
Eclipseの持つ機能を実践的に利用する


Java Solution全記事一覧



Java Agile フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Java Agile 記事ランキング

本日 月間