はじめに
EMF(Eclipse Modeling Framework)は構造化モデルの定義、およびそのモデルを編集するためのコード生成機能を備えたフレームワークです。しかし、EMFによって生成されるモデルを編集するためのエディターは、JFaceビューアーを使用したもので、モデルの視覚的表現として適さない場合があります。そこで、この記事ではGEF(Graphical Editing Framework)を使用してEMFモデルに、よりグラフィカルな表現を与えるための方法を紹介します。
対象読者
GEFを使用したアプリケーションを作成したことがある方(GEFについての一般的な説明は省きます)。
必要な環境
EMFモデルの定義
最初に、EMFモデルの定義を行います。作成するモデルは、ドラマや小説などで登場人物の関係を示すための「人物相関図」を表すものです。
このような図を表すために以下のような簡単なモデルを定義しました。人物間の関係は片方向と両方向、および未定の場合を表現できるようにしてあります。
この図を基にEMFモデルを定義します。EMFモデルの定義はいくつかの方法で行うことができますが、ここでは一番手軽な「注釈付きのJava」を使用してモデルの定義を行います。その他の方法でEMFモデルを定義する場合は、EMF SDKに付属するドキュメントを参照してください。
図2を基に作成した注釈付きのJavaインターフェイスおよびクラスのソースは以下の通りです。また、このモデルから「コア・モデル」(*.ecore
)、「生成プログラム・モデル」(*.genmodel
)の作成までを、既に作成済みのソース(model_src.zip)が、この記事冒頭のリンクからダウンロードできるので、このファイルを展開し、プロジェクトをワークスペースにインポートしてください。
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(); }
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(); }
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(); }
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]を指定してウィザードを終了します。
このエディターでは、ルート・モデルである[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
」ファイルを右クリックして[アプリケーションから開く]→[人物相関図エディター]と選択します。すると、下図のようなエディターが開かれるはずです。
この人物相関図エディターでは、EMFが生成するデフォルトのものに比べて、だいぶ人物間の関係を掴みやすくなっていると思います。
EMFモデルの読み込みと保存
EMFモデルは、先程作成したデフォルトのエディターを見れば分かるとおり、ツリー構造になっています。これは、作成した「*.correlation
」ファイルをテキスト・エディタで開いてみると良く分かると思います。したがって、読み込むモデルはルート・モデルである「CorrelationDiagram
」ということになります。
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
メソッドを使用します。
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モデルは、ルート・モデルを取得すれば階層をたどっていくことができるので、簡単にこれを実現できます。
public class DiagramEditPart extends BaseGraphicalEditPart { ... private CorrelationDiagram getCorrelationDiagram() { return (CorrelationDiagram) getModel(); } ... protected List getModelChildren() { // Characterの取得 return getCorrelationDiagram().getCharacters(); } ... }
また、人物間の関係を示す「RelationShip
」モデルはコネクションとして表現したいので以下のようにします。
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(); } ... }
あとは、これらのEditPart
をEditPartFactory
でモデルと対応付けるだけです。
モデルの変更通知
GEFアプリケーションでは、モデルが変更された場合、EditPart
で変更通知を受け、ビュー、またはEditPart
の構造を更新します。EMFモデルは、状態が変更された場合に、その通知を受ける「アダプター」を登録することができます。EMFモデルの状態が変更されると、登録されている全てのアダプターに通知されます。したがって、通知を受けるEditPart
でアダプターを管理すれば、EMFモデルの変更を知ることができます。
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
では、この情報を利用して更新処理を行います。
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()
メソッドを使用します。
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」というファクトリーを使用して作成することを推奨しています。
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アプリケーションにも簡単にプロパティー・ソース・サポートを追加することができます。
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つのフレームワークを組み合わせて使用すれば、少ない労力で高度なアプリケーションを作成できるはずです。
参考文献
- Eclipse.org - EMF SDK に付属するドキュメント、およびAPIリファレンス
- IBM RedBook "Eclipse Development using the Graphical Editing Framework and the Eclipse Modeling Framework"
- eclipse wiki
- IBM developerWorks 「Eclipse Modeling Frameworkでモデリング: 第1回」
- IBM developerWorks "Model with Eclipse Modeling Framework, Part1"
- IBM developerWorks "Model with Eclipse Modeling Framework, Part2"
- IBM developerWorks "Model with Eclipse Modeling Framework, Part3"
また、Eclipse CVS レポジトリーから入手できる、EMFとGEFを使用して、コア・モデルを作成する「org.eclipse.gef.examples.ediagram
」サンプルも参考になります。