Eclipseプラグインを作ってみよう!

第11回YAMLファイルの読み書き

前回まででGUIの実装は完了しましたので、今回はYAMLファイルの読み書きを実装し、フィールドのデータを永続化できるようにします。前回と同様にプラグインプロジェクトを用意いましたので、ダウンロードするところからはじめます。

プラグインプロジェクトのダウンロード

YAMLファイルの読み書きを実装したプラグインプロジェクトを下記のリンクからダウンロードし、お使いのワークスペースにインポートしてください。前回の連載までで実装されたプラグインプロジェクトはインポート前にリネームすることで残しておくことができます。

変更の判定

マニフェストエディターで内容を変更すると、タイトルタブのファイル名の先頭に「*」が付加され、⁠保管」コマンドが有効になります。これはどのように実装すればよいのでしょうか。第4回で拡張の設定を行いましたが、このときエディタークラスはIEditorPartインタフェースを実装していなければならないことを解説しました。このインタフェースは保管に関するメソッドを定義しているISaveablePartインタフェースを継承しています。ISaveablePartインタフェースで定義されているメソッドにはisDirty()メソッドがあります。

ISaveablePartインタフェースのisDirty()メソッド
public interface ISaveablePart {
    ...
    /**
     * Returns whether the contents of this part have changed since the last save
     * operation. If this value changes the part must fire a property listener 
     * event with <code>PROP_DIRTY</code>.
     * <p>
     * <b>Note:</b> this method is called often on a part open or part
     * activation switch, for example by actions to determine their 
     * enabled status.
     * </p>
     *
     * @return <code>true</code> if the contents have been modified and need
     *   saving, and <code>false</code> if they have not changed since the last
     *   save
     */
    public boolean isDirty();
    ...
}

JavaDocの説明を読むと、isDirty()メソッドの役割はパートのコンテンツが最後に保存されてから変更されているかどうかを返すことにあります。そこでFormDesignerEditorクラスのスーパークラスであるFormEditorクラスでisDirty()メソッドがどのように実装されているか見てみましょう。

FormEditorクラスのisDirty()メソッド
public abstract class FormEditor extends MultiPageEditorPart implements IPageChangeProvider {
    ...
    public boolean isDirty() {
        if (pages != null) {
            for (int i = 0; i < pages.size(); i++) {
                Object page = pages.get(i);
                if (page instanceof IFormPage) {
                    IFormPage fpage = (IFormPage) page;
                    if (fpage.isDirty())
                        return true;
                }
            }
        }
        return super.isDirty();
    }
    ...
}

FormEditorクラスのisDirty()メソッドではaddPage()メソッドで追加されたIFormPageクラスのisDirty()メソッドを確認していることがわかります。このように、それぞれが保持するパートやフォームを順番にチェックして、変更されたかどうかを判定していきます。

Eclipseのエディターでどのように変更の判定が行われているかを理解したところで、フォームデザイナーではどのタイミングで変更が発生したと判定するのが適切かを考えてみましょう。フォームデザイナーではフィールドの情報はテーブルビューアーが保持しています。従ってテーブルビューアーに対して変更があったときを捕捉することができればよさそうです。しかし、TableViewerクラスにはこういった用途のリスナーを登録することができないので、内容が変更される以下の場合に対して変更を通知することにしました。

  1. フィールドが追加されたとき
  2. フィールドが削除されたとき
  3. フィールドが上下に移動されたとき
  4. フィールドの詳細が変更されたとき

1~3についてはそれぞれ対応するボタンが実行されたときに、4についてはDetails(詳細)での変更通知を受け取るselectionChanged()メソッドで捕捉します。具体的にはこれらの場所でmarkDirty()メソッドを呼び出します。

変更の状態は保管されたときにリセットされる必要があります。そこでFormDeisgnerEditorクラスのdoSave()メソッドを実装します。

FormDesignerEditorクラスのdoSave()メソッド
public class FormDesignerEditor extends FormEditor {
    ...
    @Override
    public void doSave(IProgressMonitor monitor) {
        commitPages(true);
        editorDirtyStateChanged();
    }
    ...
}
}

commitPages()メソッドはisDirty()メソッドと同じように保持しているIFormPageオブジェクトやIManagedFormオブジェクトに対してcommit()メソッドを呼び出し、変更状態をリセットしていきます。これで「保管」コマンドを実行すると、変更中であることを示す「*」が消えるようになります。

JYamlの設定

これまでの実装で通常のエディターと同様の動きをするようになったので、いよいよYAMLファイルの読み書きを実装しましょう。この機能を実現するために今回はJYamlを使用します。JYamlはJavaオブジェクトまたはListやMapなどのCollection系オブジェクトとYAMLドキュメントを相互変換するためのライブラリです。今回は2008年3月12日現在の最新バージョンである1.3を使用します。

ライブラリの配置

今回は、プラグインプロジェクト以下にlibsフォルダーを作成し、その下にライブラリを配置することにします。

ビルド・パスへの追加

次にJYamlを使ったコードをコンパイルできるようにするためにjyaml-1.3.jarをビルド・パスに追加します。

jyaml-1.3.jarをビルド・パスに追加する
jyaml-1.3.jarをビルド・パスに追加する

プラグインの設定

最後にプラグイン独自の設定を行います。まずplugin.xmlを開いて、⁠ラインタイム」タブの「クラスパス」にjyaml-1.3.jarを追加します。

「クラスパス」にjyaml-1.3.jarを追加する
「クラスパス」にjyaml-1.3.jarを追加する

次にリリースしたときのことを考慮してバイナリー・ビルドにjyaml-1.3.jarを含むようにします。⁠ビルド」タブの「バイナリー・ビルド」でlibsフォルダーにチェックを入れます。

「バイナリー・ビルド」にjyaml-1.3.jarを追加する
「バイナリー・ビルド」にjyaml-1.3.jarを追加する

YAMLファイルの読み書き

それではYAMLファイルの読み書きを実装します。まず、この機能をどこに実装するかを考えてみましょう。当初私は、FormDesignerEditorクラスに実装することを考えていました。しかしFieldオブジェクトのデータはテーブルビューアーが管理しているので、YAMLファイルへの読み書きはテーブルビューアーを生成・管理している FieldsMasterSelectionPartクラスに実装することにします。

YAMLファイルの書き込み

「保管」コマンドが実行されると、FormDesignerEditorクラスのdoSave()メソッドが実行されます。先ほども解説したように、 doSave()メソッドで実行しているcommitPages()メソッドは保持しているIFormPageオブジェクトや IManagedFormオブジェクトに対してcommit()メソッドを呼び出していきます。そこでFieldsMasterSelectionPartクラスのcommit()メソッドをオーバーライドし、そこでYAMLファイルの書き込みを行うようにします。

FieldsMasterSectionPartクラスのcommit()メソッド
public class FieldsMasterSectionPart extends SectionPart implements IPartSelectionListener {
    ...
    @Override
    public void commit(boolean onSave) {
        super.commit(onSave);

        if (!onSave) {
            return;
        }

        assert (!(getManagedForm().getInput() instanceof IFileEditorInput));

        try {
            ((IFileEditorInput) getManagedForm().getInput()).getFile().setContents(
                    new ByteArrayInputStream(
                            formatYAMLString(getYAML()).getBytes("UTF-8")),  //$NON-NLS-1$
                    true,
                    false,
                    null);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (CoreException e) {
            e.printStackTrace();
        }
    }
    ...
}

変更状態をリセットするためにスーパークラスのcommit()メソッドを呼び出します。そのあとonSaveがtrueの場合にYAMLファイルへの書き込み処理を実行します。JYamlを使ったYAMLの文字列の生成部分はgetYAML()メソッドで実装しています。

保管されたYAMLファイル
- name: name
  description: 名前
  required:
    message: 名前は必須です。
- name: email
  description: メールアドレス
  required:
    message: メールアドレスは必須です。
  validator:
    - name: Email
      rule:
        allowDotBeforeAtmark: true
      message:
    - name: Compare
      rule:
        to: email2
      message: 確認用メールアドレスと一致しません。
- name: email2
  description: 確認用メールアドレス
  required:
    message: 確認用メールアドレスは必須です。

YAMLファイルの読み込み

続いてYAMLファイルの読み込みを実装します。読み込んだデータはテーブルビューアーにセットするので、テーブルビューアーの生成が完了したあとに行うことにします。

FieldsMasterSectionPartクラスのcreateTable()メソッドとsetFieldTableFromYAMLFile()メソッド
public class FieldsMasterSectionPart extends SectionPart implements IPartSelectionListener {
    ...
    private void createTable(Composite composite) {
        ...
        fViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            ...
        });

        setFieldTableFromYAMLFile();
    }

    private void setFieldTableFromYAMLFile() {
        assert (!(getManagedForm().getInput() instanceof IFileEditorInput));

        BufferedInputStream bufferedIn = null;
        ByteArrayOutputStream byteOut = null;
        try {
            bufferedIn = new BufferedInputStream(
                    ((IFileEditorInput) getManagedForm().getInput()).getFile()
                        .getContents(true));
            byteOut = new ByteArrayOutputStream();
            int read = 0;
            while ((read = bufferedIn.read()) != -1) {
                byteOut.write(read);
            }

            List<Map<String, Object>> fieldList = (List<Map<String, Object>>) Yaml.load(byteOut.toString("UTF-8"));
            for (Map<String, Object> fieldMap : fieldList) {
                fViewer.add(getField(fieldMap));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (CoreException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bufferedIn != null) {
                    bufferedIn.close();
                }
            } catch (IOException ioe) {
            }
            try {
                if (byteOut != null) {
                    byteOut.close();
                }
            } catch (IOException ioe) {
            }
        }
    }
}

完成

これでフォームデザイナーはひととおり完成です。ウィジェットの生成、データの連携、外部ライブラリーの使用、ファイルへの読み書きといった通常のアプリケーションでもよく見かける機能を解説してきましたが、いかがだったでしょうか。第2回でも解説したようにEclipse Formsはリッチクライアントの実装にも利用可能な、応用範囲の広いフレームワークです。今回の記事がEclipse Formsの理解の一助となれば幸いです。

完成したフォームデザイナー
完成したフォームデザイナー

おわりに

次回は、本連載のまとめとして有用なプラグインや情報源となるサイト・書籍をご紹介します。

おすすめ記事

記事・ニュース一覧