CodeZine(コードジン)

特集ページ一覧

EclipseのEMFモデルを使用したGEFアプリケーションの作成

EMFモデルをGEFのグラフィカル・エディターで編集する

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/03/10 00:00

EMFモデルを編集するためのアプリケーションとしてGEFのグラフィカル・ビューアーを使用する方法を紹介。

完成図
完成図

はじめに

 EMF(Eclipse Modeling Framework)は構造化モデルの定義、およびそのモデルを編集するためのコード生成機能を備えたフレームワークです。しかし、EMFによって生成されるモデルを編集するためのエディターは、JFaceビューアーを使用したもので、モデルの視覚的表現として適さない場合があります。そこで、この記事ではGEF(Graphical Editing Framework)を使用してEMFモデルに、よりグラフィカルな表現を与えるための方法を紹介します。

対象読者

 GEFを使用したアプリケーションを作成したことがある方(GEFについての一般的な説明は省きます)。

必要な環境

EMFモデルの定義

 最初に、EMFモデルの定義を行います。作成するモデルは、ドラマや小説などで登場人物の関係を示すための「人物相関図」を表すものです。

図1:連続ドラマの「人物相関図」
図1:連続ドラマの「人物相関図」

 このような図を表すために以下のような簡単なモデルを定義しました。人物間の関係は片方向と両方向、および未定の場合を表現できるようにしてあります。

図2:モデル定義
図2:モデル定義

 この図を基にEMFモデルを定義します。EMFモデルの定義はいくつかの方法で行うことができますが、ここでは一番手軽な「注釈付きのJava」を使用してモデルの定義を行います。その他の方法でEMFモデルを定義する場合は、EMF SDKに付属するドキュメントを参照してください。

 図2を基に作成した注釈付きのJavaインターフェイスおよびクラスのソースは以下の通りです。また、このモデルから「コア・モデル」(*.ecore)、「生成プログラム・モデル」(*.genmodel)の作成までを、既に作成済みのソース(model_src.zip)が、この記事冒頭のリンクからダウンロードできるので、このファイルを展開し、プロジェクトをワークスペースにインポートしてください。

「CorrelationDiagram.java」
package diagram.correlation;

import java.util.List;

/**
 * @model
 */
public interface CorrelationDiagram {

    /**
     * @model default="correlation diagram"
     */
    String getName();

    /**
     * @model type="RelationShip" containment="true"
     */
    List getRelationShips();

    /**
     * @model type="Character" containment="true"
     */
    List getCharacters();
}
「Character.java」
package diagram.correlation;

import java.util.List;

/**
 * @model
 */
public interface Character {
    /**
     * @model default="unknown"
     */
    String getName();

    /**
     * @model default="20"
     */
    int getAge();

    /**
     * @model default="role"
     */
    String getRole();

    /** 
     * @model 
     */
    int getX();

    /** 
     * @model 
     */
    int getY();

    /**
     * @model default="-1"
     */
    int getWidth();

    /** 
     * @model 
     */
    boolean isMale();

    /**
     * @model type="RelationShip" opposite="source"
     */
    List getSourceRelationShips();

    /**
     * @model type="RelationShip" opposite="target"
     */
    List getTargetRelationShips();
}
「RelationShip.java」
package diagram.correlation;


/**
 * @model
 */
public interface RelationShip {

    /**
     * @model default="outline"
     */
    String getOutline();

    /**
     * @model opposite="sourceRelationShips"
     */
    Character getSource();

    /**
     * @model opposite="targetRelationShips"
     */
    Character getTarget();

    /**
     * @model
     */
    RelationShipType getRelationShipType();
}
「RelationShipType.java」
package diagram.correlation;

/**
 * @model
 */
public class RelationShipType {

    /**
     *  @model name="Undecided"
     */
    public static final int UNDECIDED = 0;

    /**
     * @model name="OneWay"
     */
    public static final int ONE_WAY = 1;

    /**
     * @model name="TwoWay"
     */
    public static final int TWO_WAY = 2;

}

モデルを編集するためのエディターの生成

 それでは、生成プログラム・モデル(correlation.genmodel)を使用して人物相関図モデルの編集を行うエディター・プラグインを作成してみましょう。これは非常に簡単です。まず、パッケージ・エクスプローラーで「correlation.genmodel」ファイルをダブル・クリックして開いてください。

モデル・コードの生成

 ツリー形式で表示されている生成プログラム・モデルのルート・モデルを右クリックして[モデル・コードの生成]を実行します。すると、プロジェクトにいくつかのコードとプラグイン・マニフェスト・ファイルなどが追加されます。また、先程追加したインターフェイスやクラスにも変更が加えられます。この操作によって、単にインターフェイスとenumクラスを定義しただけのプロジェクトが実際に動作するモデル・プラグイン・プロジェクトになります。また、この時の変更によって「Character」インターフェイスと「CorrelationDiagram」インターフェイスに警告が表示されますが、これはこれらのインターフェイス内で使用していたjava.util.Listがこれを拡張したEListに変更されることに伴って、Listのインポート宣言が未使用になるためです。

 生成されたモデルの実装クラスは、モデルの属性や参照などが変更された場合にその通知を行う機能が追加されます。これらの実装クラスは、同時に生成されるファクトリー・クラス(CorrelationFactoryおよびCorrelationFactoryImpl)を通してのみ作成することができます。また、モデルの型や属性の型を特定するための定数を含む、パッケージ(CorrelationPackageおよびCorrelationPackageImpl)も生成されます。

編集コードの生成

 先程モデル・コードを生成したときと同じ手順で、今度は[編集コードの生成]を実行してください。すると、ワークスペースに「diagram.correlation.edit」というプロジェクトが追加されます。このプロジェクトは、EMFモデルを編集するためのアプリケーションを作成する場合に必要な機能を追加するためのものです。

 例えば、JFaceビューアーを使用してUIを作成するときにはこのプロジェクトに含まれるXXXItemProviderは、各モデルに対するコンテンツ・プロバイダーやラベル・プロバイダーとして機能します。また、ビューアーでモデルが選択された場合には、モデルにプロパティー・ソース・サポートを追加し、モデルの属性をEclipseの「プロパティー」ビューで編集することができるようにします。

エディター・コードの生成

 先程モデル・コードを生成したときと同じ手順で、「エディター・コードの生成」を実行してください。すると、ワークスペースに「diagram.correlation.editor」というプロジェクトが追加されます。このプロジェクトは、定義したモデルを編集するためのエディターと新規にモデルを作成する為のウィザードが含まれています。

生成されたエディターの実行

 これで全ての準備が整ったので、生成されたエディターを実際に使用してみましょう。まず、ランタイム・ワークベンチを起動します。ランタイム・ワークベンチのメニューから[新規]→[その他]と選択し、その後[サンプル EMF モデル作成ウィザード]→[Correlation Model]と選択します。次に、適当なファイル名と親フォルダーを指定し、ウィザードの最後のページで[Model Object]に[Diagram]を指定してウィザードを終了します。

図3
図3

 このエディターでは、ルート・モデルである[Diagram](CorrelationDiagram)を右クリックして[New Child]→[Character]とすることで「Character」モデルを追加することが出来ます。「RelationShip」を追加する場合も同様です。また、ポップアップ・メニューから[Show properties View]を選択すれば[プロパティー]ビューを表示できます。[プロパティー]ビューでは、エディターでモデルを選択したときにそのモデルの属性を編集することができます。

GEFを使用したEMFモデル・エディターの作成

 これまでの作業で、一応人物相関図を表すモデルを編集するエディターが完成しましたが、このエディターでは人物間の関係が見づらく、UIにはモデルの属性のうち1つしか表示されないので、あまり使い勝手のよいものではありません。

 そこで、モデルを編集するエディターとしてGEFのグラフィカル・ビューアーを使用したエディターを導入します。GEFを使用すれば、人物間の関係をコネクションで表すことができますし、必要な情報を全て表示することも可能です。また、EMFモデルは変更を通知する機能を備えていますし、EMFモデルはGEFアプリケーションで扱うのに適しているといえるでしょう。

 GEFを使用した人物相関図モデル・エディター・プロジェクト(correlation_GEF_src.zip)を、記事冒頭のリンクからダウンロードすることができます。このエディターを動作させるためにはワークスペースに「diagram.correlation」、および「diagram.correlation.edit」プロジェクトが必要です(これらのプロジェクトは先程作成した「モデル・コード」と「編集コード」です)。

人物相関図モデル・エディターの起動

 まず、GEFを使用した人物相関図エディターがどのようなものかを確認するために実行してみます。

 ランタイム・ワークベンチを起動し、パッケージ・エクスプローラーで先程作成した「*.correlation」ファイルを右クリックして[アプリケーションから開く]→[人物相関図エディター]と選択します。すると、下図のようなエディターが開かれるはずです。

図4
図4

 この人物相関図エディターでは、EMFが生成するデフォルトのものに比べて、だいぶ人物間の関係を掴みやすくなっていると思います。

EMFモデルの読み込みと保存

 EMFモデルは、先程作成したデフォルトのエディターを見れば分かるとおり、ツリー構造になっています。これは、作成した「*.correlation」ファイルをテキスト・エディタで開いてみると良く分かると思います。したがって、読み込むモデルはルート・モデルである「CorrelationDiagram」ということになります。

「GraphicalCorrelationEditor.java」の一部
public class GraphicalCorrelationEditor
    extends GraphicalEditorWithFlyoutPalette {
...
    private Resource resource; // モデルを含むファイル
    private CorrelationDiagram diagram; // ルート・モデル
...
    protected void setInput(IEditorInput input) {
        super.setInput(input);
        
        // エディタ入力からファイルを取得
        IFile file = ((IFileEditorInput) input).getFile();
        
        setPartName(file.getName());

        ResourceSet resourceSet; 
        if(resource == null)
            resourceSet = new ResourceSetImpl();
        else 
            resourceSet = resource.getResourceSet();
        URI uri = URI.createPlatformResourceURI(file.getFullPath()
            .toString());
        // リソースのロード
        resource = resourceSet.getResource(uri,true); 
        // ルート・モデルの取得
        diagram 
            = (CorrelationDiagram) resource.getContents().get(0);
    }
...
}

 EMFモデルを読み込むためには、まずリソース(Resource)を取得する必要があります。リソースは、EMFモデルを含むファイル(ここでは*.correlation)に対応します。また、リソースはリソース・セット(ResourceSet)によって作成/管理されます。ここでは、すでにリソース・ファイル(*.correlation)がエディター入力として渡されているので、ResourceSet#getResourceメソッドにファイルのパスをURIに変換して渡し、リソースを取得するだけです(ロードも同時に行われます)。リソースをロードしたら、ルート・モデルを取得することができます。ルート・モデルはリソースのコンテンツ・リストの先頭にあります。

補足説明
 ここではリソースのロードを行っているだけです。リソース・ファイルをゼロから作成するためには、ResourceSet#createResourceメソッドを使用します。リソース・ファイルを作成するための具体的なコードについては、EMFが生成したエディター・プロジェクトに含まれるCorrelationModelWizardクラスのperformFinishメソッド、およびcreateInitialModelメソッドを参照してください。

 リソースを保存するには、Resource#saveメソッドを使用します。

「GraphicalCorrelationEditor.java」の一部
public class GraphicalCorrelationEditor
    extends GraphicalEditorWithFlyoutPalette {
...
    private void performSave(IFile file) {
        URI uri = URI.createFileURI(file.getFullPath().toString());

        if(!uri.equals(resource.getURI()))
            resource.setURI(uri);// リソースのURIを変更する
        
        WorkspaceModifyOperation operation
            = new WorkspaceModifyOperation() {

            public void execute(IProgressMonitor monitor) {
                try {
                    // リソースの保存
                    resource.save(Collections.EMPTY_MAP);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };

        try {
            // 保存オペレーションの実行
            new ProgressMonitorDialog(getSite()
                .getShell()).run(true, false, operation);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {}

        // コマンド・スタックを更新
        getCommandStack().markSaveLocation();
    }
...
}

 saveメソッドは、保存方法を指定するためのオプションを渡すことができます。指定できるオプションは数多くあります。例えば、エンコードを変更したい場合は以下のようにします。その他のオプションについてはEMFのAPIドキュメントを参照してください。

コード例
Resource resource …
HashMap options = new HashMap();
options.put(XMLResource.OPTION_ENCODING," ISO-8859-1");
resource.save(options);

モデルとEditPartの対応付け

 GEFアプリケーションでは、一般的にモデルの構造をEditPartに対応させます。EMFモデルは、ルート・モデルを取得すれば階層をたどっていくことができるので、簡単にこれを実現できます。

「DiagramEditPart.java」の一部
public class DiagramEditPart extends BaseGraphicalEditPart {
...
    private CorrelationDiagram getCorrelationDiagram() {
        return (CorrelationDiagram) getModel();
    }
...
    protected List getModelChildren() {
        // Characterの取得
        return getCorrelationDiagram().getCharacters();
    }
...
}

 また、人物間の関係を示す「RelationShip」モデルはコネクションとして表現したいので以下のようにします。

「CharacterEditPart.java」の一部
public class CharacterEditPart extends BaseGraphicalEditPart
    implements NodeEditPart {
...
    private Character getCharacter() {
        return (Character) getModel();
    }
...
    protected List getModelSourceConnections() {
        return getCharacter().getSourceRelationShips();
    }
    
    protected List getModelTargetConnections() {
        return getCharacter().getTargetRelationShips();
    }
...
}

 あとは、これらのEditPartEditPartFactoryでモデルと対応付けるだけです。

モデルの変更通知

 GEFアプリケーションでは、モデルが変更された場合、EditPartで変更通知を受け、ビュー、またはEditPartの構造を更新します。EMFモデルは、状態が変更された場合に、その通知を受ける「アダプター」を登録することができます。EMFモデルの状態が変更されると、登録されている全てのアダプターに通知されます。したがって、通知を受けるEditPartでアダプターを管理すれば、EMFモデルの変更を知ることができます。

「BaseGraphicalEditPart.java」
public abstract class BaseGraphicalEditPart
    extends AbstractGraphicalEditPart
        implements Adapter {
    private Notifier target;

    public void activate() {
        super.activate();
        // アダプターを登録
        ((Notifier) getModel()).eAdapters().add(this);
    }

    public void deactivate() {
        // アダプターの除去
        ((Notifier) getModel()).eAdapters().remove(this);
        super.deactivate();
    }

    public Notifier getTarget() {
        return target;
    }

    public boolean isAdapterForType(Object type) {
        return type.equals(getModel().getClass());
    }

    public void setTarget(Notifier newTarget) {
        target = newTarget;
    }

    public void notifyChanged(Notification notification) {
        // このメソッドをオーバーライドして変更通知を受ける
    }
}

 ここでは、EditPartクラスでAdapterインターフェイスをインプリメントして自身をアダプターとして登録していますが、AbstractConnectionEditPartクラスのサブクラス(RelationShipEditPart)では、Adapter#getTargetメソッドがConnectionEditPart#getTarget()メソッドと重複してしまうので上記コードとは異なる方法でアダプターを登録しています。

モデルの変更をビュー、およびEditPartに反映させる

 これで、EditPartでモデルの変更通知を受け取ることができるようになったので、今度は通知された変更の種類によって適切にビュー、およびEditPartを更新できるようにします。

 EMFモデルが変更されると、登録されたアダプターのnotifyChangedメソッドが呼び出されます。このメソッドには、モデルのどの部分が、どのように変更されたかを示す情報を持つ、「Notification」オブジェクトが渡されます。変更イベントのタイプはNotification オブジェクトのgetEventTypeメソッドによって取得できます。

変更イベントのタイプ
イベント・タイプ意味
ADD子モデルが追加された場合
ADD_MANY子モデルが複数追加された場合
REMOVE子モデルが削除された場合
REMOVE_MANY子モデルが複数削除された場合
SETモデルの属性が設定された場合
UNSETモデルの属性が設定される前の状態に戻された場合

 各EditPartでは、この情報を利用して更新処理を行います。

「DiagramEditPart.java」の一部
public class DiagramEditPart extends BaseGraphicalEditPart {
...
    public void notifyChanged(Notification notification) {
        int type = notification.getEventType();
        
        switch (type) {
        case Notification.ADD:
        case Notification.ADD_MANY:
        case Notification.REMOVE:
        case Notification.REMOVE_MANY:
            refreshChildren();//子モデルの追加、または削除が行われた
            break;
        case Notification.SET: 
            refreshVisuals();
            break;
        }
    }
...
}

 また、変更された部分のみを更新したい場合は、Notification オブジェクトのgetFeatureID()メソッドを使用します。

「CharacterEditPart.java」の一部
public class CharacterEditPart extends BaseGraphicalEditPart
    implements NodeEditPart {

    public void notifyChanged(Notification notification) {
        // どの属性が変更されたかを示すIDの取得
        int id = notification.getFeatureID(Character.class);
        switch (id) {
        case CorrelationPackage.CHARACTER__X:
        case CorrelationPackage.CHARACTER__Y:
        case CorrelationPackage.CHARACTER__WIDTH:
            refreshVisuals();
        break;
        case CorrelationPackage.CHARACTER__AGE:
            refreshAge();
        break;
        …
        }
        
    }
...
}

 getFeatureIDメソッドには「Character.class」が渡されていますが、これは多重継承のモデルを使用する場合に、IDの重複を避けるために必要です。

モデルの作成

 EMFでは、モデル・クラスのインスタンスを直接作成(new)せずに、「パッケージ名+Factory」というファクトリーを使用して作成することを推奨しています。

「CorrelationModelFactory.java」
public class CorrelationModelFactory implements CreationFactory {
private EClass eClass;

    public CorrelationModelFactory(EClass eClass) {
        this.eClass = eClass;
    }

    /* 
     * ファクトリーを通して作成する
     */
    public Object getNewObject() {
        return CorrelationFactory.eINSTANCE.create(eClass);
    }

    public Object getObjectType() {
        return new Integer(eClass.getClassifierID());
    }
}

 このファクトリーのコンストラクタに渡すEClassは以下のようにして取得できます。

コード例
EClass characterEClass = CorrelationPackage.eINSTANCE.getCharacter();

プロパティー・ソース・サポートの追加

 前述のとおり、EMFが生成した「編集コード」には、各EMFモデルに対して、Eclipseの[プロパティー]ビューを使用するためのプロパティー・ソース・サポートを追加するためのコードが含まれています。これを利用すれば、GEFアプリケーションにも簡単にプロパティー・ソース・サポートを追加することができます。

「GraphicalCorrelationEditor.java」の一部
public class GraphicalCorrelationEditor
    extends GraphicalEditorWithFlyoutPalette {
...
  private ComposedAdapterFactory adapterFactory;
...
  public GraphicalCorrelationEditor() {
    setEditDomain(new DefaultEditDomain(this));

    List factories = new ArrayList();
    factories.add(new ResourceItemProviderAdapterFactory());
    factories.add(new CorrelationItemProviderAdapterFactory());
    factories.add(new ReflectiveItemProviderAdapterFactory());

    adapterFactory = new ComposedAdapterFactory(factories);
  }
...
  public Object getAdapter(Class type) {
    // プロパティ・ビュー・ページの作成
    if (IPropertySheetPage.class.equals(type)) {
      PropertySheetPage propertySheetPage
        = new PropertySheetPage();
      UndoablePropertySheetEntry entry
        = new UndoablePropertySheetEntry(getCommandStack());

      // EMF.editを使用してプロパティ・ソースを提供する
      entry.setPropertySourceProvider(
        new AdapterFactoryContentProvider(adapterFactory) {

        public IPropertySource getPropertySource(Object object) {
          // EditPartが選択された場合はそのモデルを使用して
          // IPropertySourceを作成する
          if (object instanceof EditPart)
            return super.getPropertySource(
              ((EditPart) object).getModel());

          return super.getPropertySource(object);
        }

        protected IPropertySource createPropertySource(
          Object object, IItemPropertySource itemPropertySource) {
          return new PropertySource(object, itemPropertySource) {
            public void setPropertyValue(
              Object propertyId, Object value) {
              // アンドゥ/リドゥ・コマンド用の対策
              Object adapter = adapterFactory
                             .adapt(value, IItemPropertySource.class);
              if (adapter != null) {
                IItemPropertySource propertySource
                  = (IItemPropertySource) adapter;
                value = propertySource.getEditableValue(value);
              }

              super.setPropertyValue(propertyId, value);
            }

          };
        }
      });

      propertySheetPage.setRootEntry(entry);

      return propertySheetPage;
    }

    return super.getAdapter(type);
  }
...
}

 UndoablePropertySheetEntryは、GEFアプリケーションが標準で提供するプロパティー・シート・エントリーで、[プロパティー]ビューで値が変更されたときに、内部的にコマンドを作成し、GEFのコマンド・スタックにそのコマンドを追加します。これにより、GEFアプリケーションでは、[プロパティー]ビューで行われたモデルの変更がコマンドを使用して行われるようになり、その他のコマンドと同じようにアンドゥ/リドゥが行えるようになっています。

 また、GEFのグラフィカル・ビューアーはビューアー内で選択されているEditPartをワークベンチのセレクション・サービスに提供します。プロパティー・シートは、ワークベンチの現在の選択に対してプロパティー・ソースを要求してプロパティーの編集、表示を行えるようにするので、GEFアプリケーションではEditPartからプロパティー・ソースを作成する必要があります。そこで、UndoablePropertySheetEntryクラスのgetPropertySourceメソッドをオーバーライドして、EditPartが持つEMFモデルをプロパティー・ソースの作成に使用するようにします。

 あとは、プロパティー・ソース・プロバイダーとして追加されたAdapterFactoryContentProviderに含まれる、CorrelationItemProviderAdapterFactoryがEMFモデルからプロパティー・ソースを作成してくれます。これで、EMFが生成する編集コードを使用してGEFアプリケーションにプロパティー・ソース・サポートを加えることができました。

 完全なGEFアプリケーションとして動作させるには、まだやらなければならないことが、いくつかありますが、本文中で取り上げていない部分は、一般的なGEFアプリケーションと同様ですので、ダウンロードしたソース、またはその他のGEFサンプル、およびヘルプを参考にしてください。

終わりに

 すでに、Visual EditorなどEMFとGEFを組み合わせて利用したアプリケーションは数多く見られます。EMFを使用すれば、モデルの定義や操作、およびシリアライゼーションなどについての機能を手軽に手に入れることができまし、GEFはモデルに対して、グラフィカルな表現を与えてくれます。この2つのフレームワークを組み合わせて使用すれば、少ない労力で高度なアプリケーションを作成できるはずです。

参考文献

  1. Eclipse.org - EMF SDK に付属するドキュメント、およびAPIリファレンス
  2. IBM RedBook "Eclipse Development using the Graphical Editing Framework and the Eclipse Modeling Framework"
  3. eclipse wiki
  4. IBM developerWorks 「Eclipse Modeling Frameworkでモデリング: 第1回」
  5. IBM developerWorks "Model with Eclipse Modeling Framework, Part1"
  6. IBM developerWorks "Model with Eclipse Modeling Framework, Part2"
  7. IBM developerWorks "Model with Eclipse Modeling Framework, Part3"

 また、Eclipse CVS レポジトリーから入手できる、EMFとGEFを使用して、コア・モデルを作成する「org.eclipse.gef.examples.ediagram」サンプルも参考になります。

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

修正履歴

  • 2005/10/31 19:02 対象バージョンをEclipse SDK 3.0.1、GEF SDK 3.0.1、EMF SDK 2.0.1.R200409171617 からEclipse SDK 3.1.1、GEF SDK 3.1.1、EMF SDK 2.1.1に変更。 model_src.zip、correlation_GEF_src.zip、correlation_diagram_rumtime.zipをEclipse 3.1.1に対応したものに変更。 見出し「プロパティー・ソース・サポートの追加」以下のソース「GraphicalCorrelationEditor.javaの一部」を変更。

あなたにオススメ

著者プロフィール

  • TAK_A(タカハシアキラ)

    Eclipseに関するコアな情報を提供する「ObserveEclipse」の管理人。なぜか海外の開発者から英訳してほしいとの依頼が日本語のメールで舞い込むことがあるとのこと。

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5