最終更新日: 2004 年 6 月 1 日
この資料は、EMF の基本的な概要と、そのコード生成プログラムのパターンを示しています。 EMF のすべての機能に関する詳細については、 「Eclipse モデリング・フレームワーク」(Addison Wesley, 2003)、 またはフレームワーク・クラス自体を対象とした Javadoc を参照してください。
EMF は、構造化モデルを基にして、ツールおよびその他のアプリケーションをビルドするための、Java フレームワークおよびコード生成機能です。 オブジェクト指向モデリングの概念をすでに持つ皆様にとっては、EMF は、ご使用のモデルを、効果的かつ正確でカスタマイズ可能な Java のコードに手軽に変換するのに役立ちます。 必ずしも正式なモデルの価値はよく分からないという皆様にとっても、EMF は、同様な利点を非常に安いコストで提供するようになっています。
ところで、モデルとはどのような意味でしょうか。 モデリングについて語られるとき、一般に考えられるのは、クラス図、コラボレーション図、状態遷移などのようなものです。 UML (統一モデリング言語) は、これらの種類のダイアグラムについての標準表記を定義しています。 UML ダイアグラムの組み合わせを使用すると、一つのアプリケーションの完全なモデルを明示することができます。 このモデルは純粋に文書として使用するか、あるいは適切なツールがあれば、アプリケーションの一部分、または単純なケースならば一つのアプリケーション全体を生成するための入力に使用することが可能です。
この種のモデリングは一般的にオブジェクト指向分析および設計 (OOA/D) の高価なツールを必要とするので、EMF がコストをかけずに提供するという上記の表現に疑問を持たれるかもしれません。 低価格である理由は、EMF モデルが必要とするのは UML 内にモデル化できる種類のほんの小さなサブセット (具体的に言うと、クラスの単純な定義とその属性および関係) で、完全なグラフィック・モデリング・ツールを必要としないからです。
EMF が XMI (XML Metadata Interchange) をモデル定義の正規フォームとして使用するとき[1] 、モデルをこのフォームにするには幾つかの方法があります。
1 番のアプローチはもっとも直接的ですが、一般的に XML 上級者しか対応できません。 2 番目の選択は、完全なモデリング・ツールをすでにご利用になっている場合には、もっとも望ましいものです。 3 番目のアプローチは、単に基本 Java 開発環境 (例えば、Eclipse の Java 開発ツール) を使用して、EMF およびそのコード生成プログラムの利点を低価格で活用する方法を、Java プログラマーに提供します。 最後のアプローチは、特定の XML ファイル・フォーマットの読み取りまたは書き込みが必要なアプリケーションの作成の場合に最も適しています。
EMF モデルの指定を行うと、EMF 生成プログラムは、対応する Java 実装クラスのセットを作成することができます。 これらの生成されたクラスを編集して、メソッドやインスタンス変数を追加し、必要に応じてさらにモデルから再生成を行うことができます。再生成しても追加された内容は保持されます。 ただし追加されたコードが、モデルで変更した内容に依存している場合は、さらにコードを修正してその変更を反映する必要があります。そうしないと、コードにモデルの変更および再生成が全く反映されません。
EMF を使用してアプリケーションを作成することにより、単純に生産性が向上する以外にも、いくつかの他の利点、たとえばモデルの変更の通知、デフォルトの XMI およびスキーマ・ベースの XML シリアライゼーションを含むパーシスタンス (永続性) のサポート、モデル検証のフレームワーク、および一般的な EMF オブジェクトの操作において非常に効果的な reflective API なども提供されます。 その中で最も重要なのは、EMF が、他の EMF ベースのツールやアプリケーションとのインターオペラビリティーのベースを提供するということです。
EMF は、2 つの基本的なフレームワークから成り立っています。コア・フレームワークと EMF.Edit です。コア・フレームワークは、基本的な生成とランタイム・サポートを提供し、モデルに対応する Java 実装クラスを作成します。 EMF.Edit は、コア・フレームワークを拡張し、コア・フレームワーク上にビルドされ、モデルの表示とコマンド・ベースの (undo 可能な) 編集、およびモデル編集機能の基本作業も可能にするアダプター・クラスの生成を追加します。 以下のセクションで、コア EMF フレームワークの主な機能を説明します。 EMF.Edit は、 「EMF.Edit フレームワークの概説」という別の文書で説明されています。 EMF および EMF.Edit 生成プログラムの実行方法の説明は、「チュートリアル:EMF モデルの生成」を参照してください。
OMG (オブジェクト管理グループ) MOF (メタオブジェクト・ファシリティー) をご存知の方は、EMF とはどのように関係するのか不思議に思うかもしれません。 実際、EMF は MOF 仕様の実装として始まりましたが、EMF を使用して大規模なツールのセットを実装したことから得られた経験を基にして、さらに発展しました。 EMF は、MOF API のコア・サブセットの高度に効果的な Java による実装と考えることができます。 しかし、混乱を避けるため、EMF 内における MOF のようなコア・メタ・モデルを Ecore と呼びます。
現在の MOF 2.0 のプロポーザルでは、MOF モデルと同様のサブセットは、EMOF (Essential MOF) と呼ばれて分離されています。 Ecore と EMOF の間には、少々 (たいていはネーミング上の) 違いがあります。しかし、EMF は EMOFのシリアライゼーションを透過的に読み取ったり書き込んだりすることができます。
EMF をわかりやすく説明するために、以下のような、クラスが 1 つの小さなモデルを想定するところから始めましょう。
このモデルは、Book という単一のクラスを示しており、2 つの属性、String 型の title と int 型の pages があります。
このモデルの定義はごく単純ですが、EMF コード生成プログラムに提供される場合に多くの方法があります。
EMF と連動するモデリング・ツールを使用する場合[2] 、上に表示したような単純なクラス図を作図します。
別の方法として、次のように XMI 文書内で直接、モデルを記述することもできます。
<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="library "nsURI="http:///library.ecore" nsPrefix="library"> <eClassifiers xsi:type="ecore:EClass" name="Book"> <eStructuralFeatures xsi:type="ecore:EAttribute" name="title" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/> <eStructuralFeatures xsi:type="ecore:EAttribute" name="pages" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/> </eClassifiers> </ecore:EPackage>
XMI 文書は、クラス図とすべて同じ情報を含んでいますが、簡潔さの面では多少劣ります。 ダイアグラムにおけるすべてのクラスと属性は、XMI 文書において対応するクラスまたは属性の定義を持っていることになります。
グラフィカル・モデリング・ツールがなく、また XMI 構文を全部手で入力するという気もない人には、モデルを記述するのに 3 つめのオプションが使用可能です。 EMF 生成プログラムは、コードをマージする生成プログラムなので、部分的な (モデル情報の注釈の付いた) Java インターフェースをあらかじめ提供しておくと、生成プログラムはインターフェースを生成メタデータとして使用し、生成したコードを残りの実装とマージすることができます。
以下のように、Book モデル・クラスを Java で定義することができます。
/** * @model */ public interface Book { /** * @model */ String getTitle(); /** * @model */ int getPages(); }
このアプローチによって、Java インターフェースの形式のモデルのすべての情報に、属性および参照を確認する標準 get メソッド [3] を提供します。 @model タグは、コード生成プログラムに対し、どのインターフェース、およびインターフェースの一部がモデル要素に対応するかを識別し、コード生成を要求するのに使用します。
上記の単純な例では、このインターフェースの Java イントロスペクションを通して、すべてのモデルの情報が実際に使用可能になるため、追加のモデル情報は必要ありません。 しかし一般的なケースでは、@model タグの後に、モデル要素の詳細が追加される場合もあります。 例えば、ページ属性を読み取り専用 (つまり、set メソッドを生成しない) にしたい場合、注釈に以下のように追加する必要があります。
/** * @model changeable="false" */ int getPages();
デフォルトと異なる場合のみ情報を指定する必要があるので、通常注釈は簡単で簡潔です。
モデルのインスタンス・シリアライゼーションをどのようにするかを指定するスキーマで、モデルを記述したい場合があるかもしれません。 これは、XML を使用して、既存のアプリケーションを統合する、あるいは標準に準拠したアプリケーションを作成する場合に便利です。 以下に、先ほどの簡単な Book モデルに相当するスキーマを指定する方法を示します。
<xsd:schema targetNamespace="http:///library.ecore" xmlns="http:///library.ecore" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:complexType name="Book"> <xsd:sequence> <xsd:element name="title" type="xsd:string"/> <xsd:element name="pages" type="xsd:integer"/> </xsd:sequence> </xsd:complexType> </xsd:schema>
このアプローチは、多少、他の 3 つのアプローチと異なります。これは、主に、EMF が、結果的に使用するシリアライゼーションに一定の制限を適用して、スキーマとの整合性を図る必要があるためです。 結果として、スキーマ用に作成されたモデルは、他のいずれかの方法で指定されたものとは、少し異なってきます。 これらの差異の詳細については、この概説では扱いません。
以降、この文書では、明快で簡潔なため UML ダイアグラムを使用します。 ここで説明するすべてのモデリング概念は、注釈付き Java を使用して表現することも、また直接 XMI を使用して表現することもでき、ほとんどが相当する XML スキーマを持っています。 どのように情報を提供するかにかかわらず、EMF によって生成されるコードは同じです。
モデル内のそれぞれのクラスごとに、Java インターフェースおよび対応する実装クラスが生成されます。 先程の例では、Book に生成されるインターフェースは以下のようになります。
public interface Book extends EObject { String getTitle(); void setTitle(String value); int getPages(); void setPages(int value); }
生成された各インターフェースには、対応するモデル・クラスのそれぞれの属性および参照ごとに、getter および setter メソッドが含まれています。
インターフェース Book は、基本インターフェース EObject を拡張します。 EObject は、EMF で java.lang.Object に相当するもので、これがすべての EMF クラスのベースです。 EObject およびその対応する実装クラス EObjectImpl (これについては後ほど説明します) は、Book を EMF 通知フレームワークおよびパーシスタンス・フレームワークに関与させる、軽量のベース・クラスを提供します。 EObject が正確には何をその中に持ち込むのかを考察する前に、EMF がどのように Book を生成するかを見ることにしましょう。
生成された各実装クラスには、対応するインターフェース内で定義されている getter および setter に加えて、EMF フレームワークに必要な他のいくつかのメソッドの実装が含まれています。
BookImpl クラスには、title と pages のアクセサーの実装が含まれています。 例えば、pages 属性は、以下の実装として生成されます。
public class BookImpl extends EObjectImpl implements Book { ... protected static final int PAGES_EDEFAULT = 0; protected int pages = PAGES_EDEFAULT; public int getPages() { return pages; } public void setPages(int newPages) { int oldPages = pages; pages = newPages; if (eNotificationRequired()) eNotify(new ENotificationImpl(this, Notification.SET, ..., oldPages, pages)); } ... }
生成された get メソッドは、最適化されています。 単純に、属性に相当するインスタンス変数を戻します。
set メソッドは、もう少し複雑ですが、かなり効果的です。 インスタンス変数 pages の値の設定に加えて、set メソッドは、オブジェクトを listen している可能性のあるすべてのオブザーバーに、eNotify() メソッドを呼び出して変更通知を送信する必要があります。 オブザーバーがいない場合 (例えば、バッチ・アプリケーションの場合) を最適化するために、通知オブジェクト (ENotificationImpl) の作成および eNotify() の呼び出しは、eNotificationRequired() の呼び出しによってガードされています。 eNotificationRequired() のデフォルトの実装は、単に、オブジェクトに接続しているオブザーバー (アダプター) が存在するかどうかをチェックします。 したがって、EMF オブジェクトがオブザーバーなしで使用されている場合は、eNotificationRequired() への呼び出しは、JIT コンパイラーの使用時はインライン化されて効果的に、NULL ポインターをチェックするにすぎません。
ストリング・タイプの title 属性のように、その他のタイプの属性の生成されたアクセサーは、多少違いはあるものの、基本的には pages で示したパターンと同じです[4] 。
参照の場合、生成されるアクセサーは、特に両方向のものはもう少し複雑で、ここに EMF 生成プログラムの真の価値が表れ始めます。
このモデルの例を拡張して、Book クラスと関連を持つもう一つの別のクラス Writer を作りましょう。
book とその writer の間の関連は、この例では、単一の片方向参照です。 book からその writer のアクセスに使用される参照 (役割) 名は、author とします。
EMF 生成プログラムを通してこのモデルの生成を実行すると、新しいインターフェース Writer および実装クラス WriterImpl を生成することに加えて、Book インターフェース内に以下の get メソッドおよび set メソッドの生成が追加されます。
Writer getAuthor(); void setAuthor(Writer value);
author 参照は片方向なので、setAuthor() メソッドの実装は、前の setPages() のと同様に、ほとんど簡単なデータ setter です。
public void setAuthor(Writer newAuthor) { Writer oldAuthor = author; author = newAuthor; if(eNotificationRequired()) eNotify(new ENotificationImpl(this, ...)); }
ただ 1 つの違いは、ただの単純なデータ・フィールドではなく、ここではオブジェクトのポインターを設定していることです。
ここで扱うのがオブジェクト参照であるため、getAuthor() メソッドはしかし、もう少し複雑になります。 これは、author タイプを含めてある種の参照タイプの get メソッドでは、参照されたオブジェクト (このケースでは Writer) が元のオブジェクト (このケースでは Book) とは異なるリソース (文書) 内に存続している可能性に対処する必要があるためです。 EMF パーシスタンス・フレームワークは、遅延ロード方式を使用するので、オブジェクト・ポインター (このケースでは author) は、ある時点では、実際に参照されているオブジェクト [5] ではなく、オブジェクトのプロキシーである可能性があります。 結果として、getAuthor() メソッドは以下のようになります。
public Writer getAuthor() { if (author != null && author.eIsProxy()) { Writer oldAuthor = author; author = (Writer)eResolveProxy((InternalEObject)author); if (author != oldAuthor) { if (eNotificationRequired()) eNotify(new ENotificationImpl(this, Notification.RESOLVE, ...)); } } return author; }
単に author インスタンス変数を戻す代わりに、まず、継承されたフレームワーク・メソッドである eIsProxy() メソッドを呼び出し、参照がプロキシーかどうかをチェックし、もしそうなら、次に eResolveProxy() を呼び出します。 後者のメソッドは、静的ユーティリティー・メソッドである EcoreUtil.resolve() を呼び出して、プロキシーの URI を使用してターゲット・オブジェクトの文書をロードし、結果としてオブジェクトをロードします。 成功すれば、解決済みオブジェクトを戻します。 しかし、文書のロードに失敗すれば、再びプロキシーを戻すだけです[6] 。
ある種の参照の場合、プロキシーの解決が get パターンにどのように影響を与えているかが理解できたので、関連が両方向に指定されたとき、set パターンがどのように変化するかを考察しましょう。 片方向 author 関連を次のように変更しましょう。
関連のラインの Writer 側の端の矢印がないことが示すように、関連は両方向になりました。 Writer から Book へのアクセスに使用される役割の名前は、books です。
モデルを再生成すると、getAuthor() メソッドは影響を受けませんが、 setAuthor() は以下のようになります。
public void setAuthor(Writer newAuthor) { if (newAuthor != author) { NotificationChain msgs = null; if (author != null) msgs = ((InternalEObject)author).eInverseRemove(this, ..., msgs); if (newAuthor != null) msgs = ((InternalEObject)newAuthor).eInverseAdd(this, ..., msgs); msgs = basicSetAuthor(newAuthor, msgs); if (msgs != null) msgs.dispatch(); } else if (eNotificationRequired()) eNotify(new ENotificationImpl(this, ...)); // "touch" 通知を送信 }
お分かりのように、author のような両方向参照の設定を行う時は、参照のもう一方の端も (eInverseAdd() を呼び出すことによって) 設定しなければなりません。 また、(eInverseRemove() を呼び出すことによって) すべて前の author の逆の参照を除去する必要があります。というのも、このモデル内では、author 参照は単数 (つまり、book は 1 つの author しか持てない) なので[7] 、 この book は、複数の Writer の book 参照として存在できません。 最終的に、author 参照は、もう一つの生成されたメソッド (basicSetAuthor()) を呼び出すことによって設定します。これは、以下のようになります。
public NotificationChain basicSetAuthor(Writer newAuthor, NotificationChain msgs) { Writer oldAuthor = author; author = newAuthor; if (eNotificationRequired()) { ENotificationImpl notification = new ENotificationImpl(this, ...); if (msgs == null) msgs = notification; else msgs.add(notification); } return msgs; }
このメソッドは、msgs 引き数がヌルでない場合、直接実行する代わりに、通知が追加されることを除けば、片方向参照の set メソッドと大変よく似ています[8] 。 両方向参照の設定の操作では、すべての順方向/逆方向の追加/除去が起きるので、最大 4 つの異なる通知 (この例の場合では 3 つ) が生成されます。 NotificationChain は、すべてのこれらの個々の通知を収集し、すべての状態変更が完了するまでその実行を据え置きするために使用されます。 キューに入れられた通知は、上記の setAuthor() メソッドが 示すように msgs.dispatch() の呼び出しによって送信されます。
この例の中で、book の関連 (Writer から Book へ) の多重度が many である (つまり、0..*) ことに気が付かれたかもしれません。 言い換えれば、1 人の writer は複数の book を書いたことがあるかもしれません。 EMF 内の多重度が多の参照 (つまり、上限が 1 より大きい参照) は、コレクション API を使用して取り扱われるので、インターフェース内では、get メソッドだけが生成されます。
public interface Writer extends EObject { ... EList getBooks(); }
getBooks() が java.util.List の代わりに EList を戻すことに注意してください。 実際には、あまり違いはありません。EList は、java.util.List の EMF におけるサブクラスで、2 つの移動メソッドが API に追加されたものです。 それ以外には、クライアントの観点からは、標準 Java の List と同様に考えることができます。 例えば、一つの book を books 関連に追加するには、単純に以下のように呼び出すことができます。
aWriter.getBooks().add(aBook);
あるいは、繰り返し実行するには、以下のようにします。
for (Iterator iter = aWriter.getBooks().iterator(); iter.hasNext(); ) { Book book = (Book)iter.next(); ... }
お分かりのように、クライアントの観点からは、多重度が多である参照を取り扱う API は、特殊なものではありません。 しかし、books 参照は両方向関連の一部なので (逆は Book.author です)、 setAuthor() メソッドで示されているように、やはりすべての逆関連の厄介なハンドシェークを行う必要があります。 WriterImpl 内の getBooks() メソッドの実装を見ると、多重度が多の場合どのように取り扱われるか理解できます。
public EList getBooks() { if (books == null) { books = new EObjectWithInverseResolvingEList(Book.class, this, LibraryPackage.WRITER__BOOKS, LibraryPackage.BOOK__AUTHOR); } return books; }
getBooks() メソッドは、特殊な実装クラス、 EObjectWithInverseResolvingEList を戻します。これは、追加および除去における呼び出しの時、逆方向ハンドシェークを行うために必要なすべての情報をもって作成されています。 EMF は実際に、20 の異なる、特化された EList 実装を提供し[9] 、 すべてのタイプの多重度多のフィーチャーを効果的に実装します。 片方向関連の場合 (つまり、逆がない場合) は、 EObjectResolvingEList を使用します。 プロキシー解決を必要としない参照の場合は、EObjectWithInverseEList または EObjectEList などを使用します。
したがってこの例で、books 参照を実装するために使用されるリストは、LibraryPackage.BOOK__AUTHOR (リバース機能を表現する、生成された static int 定数) を引き数にして作成されています。 これは、add() 呼び出し時に使用されて、Book の eInverseAdd() を呼び出します。setAuthor() 時に Writer で eInverseAdd() が呼び出されたのと同様の方法です。 BookImpl クラスの eInverseAdd() は以下のようになります。
public NotificationChain eInverseAdd(InternalEObject otherEnd, int featureID, Class baseClass, NotificationChain msgs) { if (featureID >= 0) { switch (eDerivedStructuralFeatureID(featureID, baseClass)) { case LibraryPackage.BOOK__AUTHOR: if (author != null) msgs = ((InternalEObject)author).eInverseRemove(this, .., msgs); return basicSetAuthor((Writer)otherEnd, msgs); default: ... } } ... }
最初に eInverseRemove() を呼び出して、(以前、setAuthor() メソッドを見た時に説明したように) 前の author をすべて除去します。それから、basicSetAuthor() を呼び出して、実際に参照を設定します。 この例の場合 1 つの両方向参照しか持っていないにもかかわらず、eInverseAdd() は Book クラスに存在しうるすべての両方向参照のために case 一つを含む switch ステートメントを使用しています[10] 。
Book に対するコンテナーとして振る舞う新規クラス、Library を追加しましょう。
包含参照は、関連の Library の側の端にある黒のダイヤモンドで示されます。 塗りつぶしは、この関連は Library が、0 またはそれ以上の値の Book の集約であることを示しています。 値による集約 (包含) 関連は、ターゲットのインスタンスの親または所有関係を識別するので特に重要です。これは、永続時のオブジェクトの物理的ロケーションを暗示しています。
包含は、いくつかの方法で生成されるコードに影響します。 まず最初に、包含されるオブジェクトは、そのコンテナーと同じリソース内にあることが保証されているので、プロキシー解決は必要ありません。 したがって、 LibraryImpl に生成される get メソッドは、解決を行わない EList 実装クラスを使用します。
public EList getBooks() { if (books == null) { books = new EObjectContainmentEList(Book.class, this, ...); } return books; }
プロキシー解決を行わないことに加えて、EObjectContainmentEList は contains() オペレーションを大変効果的に実装しています (つまり、特定の時間か、通常は永続的に)。 これは特に重要で、EMF 参照リストでは重複する項目は許されないので、contains() もまた add() オペレーション時に呼び出されます。
1 つのオブジェクトは 1 つのコンテナーにしか入れないので、包含関連のオブジェクトを追加するということは、実際の関連にかかわらず、現在それが入っているコンテナーからオブジェクトを除去することも意味します。 例えば、ライブラリーの books リストに一つの Book を追加することは、他のライブラリーの books リストからそれを除去することになります。 多重度 1 を持つあらゆる両方向関連の場合と同様です。ここではしかし、Writer クラスも Book に対する、ownedBooks と呼ばれる包含関連を持っていると仮定しましょう。 その場合、もしある book インスタンスがいずれかの Writer の ownedBooks リストにある場合、そのインスタンスをライブラリーの books 参照に追加する時は、まず、Writer から除去する必要があります。
この種のものを効果的に実装するために、ベース・クラス EObjectImpl は EObject タイプのインスタンス変数 (eContainer) を持ち、一般的なコンテナーを保持するために使用します。 結論として、包含参照は、常に暗黙的に両方向の関係があります。 Book から Library にアクセスには、以下のように書くことができます。
EObject container = book.eContainer(); if (container instanceof Library) library = (Library)container;
ダウンキャストを回避したい場合は、関連を変更して、以下のように明示的に両方向にすることができます。
そして、EMF に、タイプが異なっても支障のないちゃんとした get メソッドを生成させましょう。
public Library getLibrary() { if (eContainerFeatureID != LibraryPackage.BOOK__LIBRARY) return null; return (Library)eContainer; }
この明示的な get メソッドは、前に非コンテナー参照 (上記 getAuthor() のような) の例で見たような、生成されたインスタンス変数ではなく、EObjectImpl からの eContainer 変数を使用します[11] 。
これまで、EMF が単純な属性やさまざまなタイプの参照をどのように扱うかを見てきました。 その他によく使用されるタイプの属性が、列挙型です。 列挙タイプの属性は、Java の、タイプが異なっても支障のない enum パターン [12] を使用して実装されます。
列挙型属性の category を Book クラスに追加して、
実装クラスを再生成すると、今度は Book インターフェースに category の getter および setter が含まれます。
BookCategory getCategory(); void setCategory(BookCategory value);
生成されるインターフェースで、カテゴリー・メソッドは BookCategory と呼ばれる、タイプ・セーフな列挙クラスを使用します。このクラスは、列挙型の値の静的定数とその他の便利なメソッドを、以下のように定義します。
public final class BookCategory extends AbstractEnumerator { public static final int MYSTERY = 0; public static final int SCIENCE_FICTION = 1; public static final int BIOGRAPHY = 2; public static final BookCategory MYSTERY_LITERAL = new BookCategory(MYSTERY, "Mystery"); public static final BookCategory SCIENCE_FICTION_LITERAL = new BookCategory(SCIENCE_FICTION, "ScienceFiction"); public static final BookCategory BIOGRAPHY_LITERAL = new BookCategory(BIOGRAPHY, "Biography"); public static final List VALUES = Collections.unmodifiableList(...)); public static BookCategory get(String name) { ... } public static BookCategory get(int value) { ... } private BookCategory(int value, String name) { super(value, name); } }
ここで示されるように、列挙型クラスには、列挙型の値のための static int 定数と、列挙型の リテラル・オブジェクト自身のためのそれぞれの static 定数があります。 int 定数は、モデルのリテラル名 [13] と同じ名前を持っています。 リテラル定数は、同様に _LITERAL がついた名前を持っています。
定数は、リテラルへの便利なアクセスを提供します。例えば book のカテゴリーを設定する場合、
book.setCategory(BookCategory.SCIENCE_FICTION_LITERAL);
BookCategory コンストラクターは private なので、存在する列挙型クラスのインスタンスは、static の MYSTERY_LITERAL、SCIENCE_FICTION_LITERAL、および BIOGRAPHY_LITERAL として使用されるもののみです。 結果として、同等比較 (つまり、.equals() 呼び出し) は必要になることはありません。 リテラルは、より単純でより効果的な == 演算子を以下のように使用して、常に信頼性をもって比較することができます。
book.getCategory() == BookCategory.MYSTERY_LITERAL
多数の値を比較する時、int 値を使用する switch ステートメントは、さらに便利です。
switch (book.getCategory().value()) { case BookCategory.MYSTERY: // do something ... break; case BookCategory.SCIENCE_FICTION: ... }
リテラル名 (ストリング) または値 (int) だけが使用可能な場合、対応するリテラル・オブジェクトを検索するのに使用できる便利な get() メソッドもまた、Enumeration クラスで生成されます。
モデル・インターフェースと実装クラスに加えて、EMF は少なくともあと 2 つのインターフェース (および実装クラス) を生成します。ファクトリーとパッケージです。
ファクトリーは、その名前が示しているように、モデル・クラスのインスタンスの作成に使用されます。一方、パッケージは、いくつかの静的定数 (例えば、生成されたメソッドが使用するフィーチャー定数) およびモデルのメタデータ [14] にアクセスする便利なメソッドを提供します。
以下に、book に対するファクトリー・インターフェースの例を示します。
public interface LibraryFactory extends EFactory { LibraryFactory eINSTANCE = new LibraryFactoryImpl(); Book createBook(); Writer createWriter(); Library createLibrary(); LibraryPackage getLibraryPackage(); }
ここで示されているように、生成されたファクトリーは、モデル内で定義された各クラスの作成メソッド (create)、モデルのパッケージのアクセサー、および一個のファクトリーへの static 定数参照 (すなわち eINSTANCE) を提供します。
LibraryPackage インターフェースは、モデルのすべてのメタデータへの便利なアクセスを提供します。
public interface LibraryPackage extends EPackage { ... LibraryPackage eINSTANCE = LibraryPackageImpl.init(); static final int BOOK = 0; static final int BOOK__TITLE = 0; static final int BOOK__PAGES = 1; static final int BOOK__CATEGORY = 2; static final int BOOK__AUTHOR = 3; ... static final int WRITER = 1; static final int WRITER__NAME = 0; ... EClass getBook(); EAttribute getBook_Title(); EAttribute getBook_Pages(); EAttribute getBook_Category(); EReference getBook_Author(); ... }
ご覧のとおり、メタデータは 2 つの形式で使用できます。int 定数と Ecore メタ・オブジェクト自身です。 int 定数は、メタ情報の受け渡しに非常に効果的な方法を提供します。 生成されるメソッドが、その実装内でこれらの定数を使用していることにお気付きかもしれません。 あとで、EMF アダプターをどのように実装できるかを考察する時に、定数もまた、通知の処理時に何が変更されたかを判断するために、最も効果的な方法を提供していることが分かるでしょう。 また、ちょうどファクトリーのように、生成されたパッケージ・インターフェースは、その唯一の実装への static 定数参照を持ちます。
たとえば、Book モデル・クラスのサブクラスとして、SchoolBook を次のように作成します。
EMF 生成プログラムは通常、単一継承を処理します。生成されたインターフェースはスーパー・インターフェースを拡張します。
public interface SchoolBook extends Book
また、実装クラスはスーパー実装クラスを継承します。
public class SchoolBookImpl extends BookImpl implements SchoolBook
Java と同じく、複数のインターフェースの継承がサポートされていますが、各 EMF クラスは 1 つの実装ベース・クラスからしか継承できません。 このため、多重継承のモデルがあるときには、複数のベースのどれを実装ベース・クラスとして使用するか、識別する必要があります。 それ以外は、単純にインターフェースの合成として扱われ、それらのインプリメンテーションは派生した実装クラスに生成されます。
次の例を見てみましょう。
この例では Book と Asset という 2 つのクラスから派生する SchoolBook を作成しています。ここで示すように[15] 、実装ベース (継承元) クラスを Book としました。 モデルを再生成すると、インターフェース SchoolBook は今度は 2 つのインターフェースを継承することになります。
public interface SchoolBook extends Book, Asset
この実装クラスは以前と大体同じように見えますが、今回だけ合成されたメソッドの実装 getValue() および setValue() が含まれています。
public class SchoolBookImpl extends BookImpl implements SchoolBook { public float getValue() { ... } public void setValue(float newValue) { ... } ... }
ユーザーは、あとでモデルを変更し、再生成する場合にも変更内容を失う心配なく、生成された Java クラスに振る舞い (メソッドとインスタンス変数) を追加できます。 たとえば、クラス Book にメソッド isRecommended() を追加してみましょう。これを行うためには、新規にメソッド・シグニチャーを、単純に Java インターフェース Book に追加します。
public interface Book ... { boolean isRecommended(); ... }
また、この実装はクラス BookImpl に追加されます。
public boolean isRecommended() { return getAuthor().getName().equals("William Shakespeare"); }
これは最初の生成されたメソッドとは異なるので、EMF 生成プログラムはこの変更を消去しません。 EMF によって生成された各メソッドには次のような @generated タグを含む Javadoc コメントが組み込まれています。
/** * ... * @generated */ public String getTitle() { return title; }
このタグを含まない、ファイル中の任意のメソッド (isRecommended() など) は、たとえ再生成を行ってもそのまま残されます。 実は、生成されたメソッドの実装を変更したい場合は、そのメソッドから @generated タグを削除すれば変更できます。[16]
/** * ... *@generated*/ public String getTitle() { // our custom implementation ... }
上記の場合、@generated タグがなくなったために getTitle() メソッドはユーザー・コードとみなされます。ここでモデルを再生成すると、生成プログラムは衝突を検出し、生成されたメソッドの方を廃棄します。
実際には、生成されたメソッドが廃棄される前に、生成プログラムはまず、ファイル内に同じ名前で、Gen が追加された生成済みメソッドがほかにないかを調べます。 もしあった場合は、新たに生成されたバージョンのメソッドを廃棄する代わりに、出力をそれにリダイレクトします。 たとえば、生成済みの getTitle() 実装を継承したい場合には、それを完全に廃棄する代わりに、次のように名前を変更するだけでよいのです。
/** * ... * @generated */ public String getTitleGen() { return title; }
それから、任意の内容を実行させるユーザー・メソッドとしてオーバーライドを追加します。
public String getTitle() { String result = getTitleGen(); if (result == null) result = ... return result; }
ここで再生成を行えば、生成プログラムは getTitle() のユーザー・バージョンがあるため衝突を検出しますが、クラスに @generated getTitleGen() メソッドがあるため、新規に生成された実装を廃棄する代わりにこのメソッドにリダイレクトします。
属性と参照のほかに、ユーザーのモデル・クラスに操作を追加できます。 操作を追加すると、EMF 生成プログラムはインターフェースにシグニチャーを生成し、実装クラスにメソッドのスケルトンを生成します。 EMF は振る舞いのモデル化を行わないため、実装はユーザー作成の Java コーディングによって準備されなければなりません。
これは、前述したように、生成済み実装から @generated タグを削除し、その場所にコードを追加することで実行できます。 または、Java コーディングをモデルに適当に含めることもできます。Rose では「操作指定 (Operation Specification)」ダイアログの「セマンティクス (Semantics)」タブのテキスト・ボックスにコードを入力できます。 このコードは、操作 [17] の注釈として EMF モデルに格納され、本文に生成されます。
生成されたクラスを使用して、クライアント・プログラムから以下のような単純な Java のステートメントで Book を作成し、初期化できます。
LibraryFactory factory = LibraryFactory.eINSTANCE; Book book = factory.createBook(); Writer writer = factory.createWriter(); writer.setName("William Shakespeare"); book.setTitle("King Lear"); book.setAuthor(writer);
Book から Writer への関連付け (author) は両方向なので、逆参照 (books) が自動的に初期化されます。 これは、以下のように books の参照を繰り返すことによって検証できます。
System.out.println("Shakespeare books:"); for (Iterator iter = writer.getBooks().iterator(); iter.hasNext(); ) { Book shakespeareBook = (Book)iter.next(); System.out.println(" title: " + shakespeareBook.getTitle()); }
このプログラムを実行すると、以下のような出力が作成されます。
Shakespeare books: title: King Lear
上記のモデルを含む、mylibrary.xmi という名前の文書を作成するには、プログラムの最初で EMF リソースを作成し、book と writer をリソースに書き込み、最後に save() を呼び出すだけです。
// Create a resource set. ResourceSet resourceSet = new ResourceSetImpl(); // Get the URI of the model file. URI fileURI = URI.createFileURI(new File("mylibrary.xmi").getAbsolutePath()); // Create a resource for this file. Resource resource = resourceSet.createResource(fileURI); // Add the book and writer objects to the contents. resource.getContents().add(book); resource.getContents().add(writer); // Save the contents of the resource to the file system. try { resource.save(Collections.EMPTY_MAP); } catch (IOException e) {}
リソース・セット (インターフェース ResourceSet) を使って EMF リソースを作成することに注意してください。 リソース・セットは、文書の相互参照の可能性があるリソースを管理するために、EMF フレームワークによって使用されます。 レジストリー (インターフェース Resource.Factory.Registry) を利用して、スキーマ、ファイル拡張子、その他の考えられる基準に基づき、指定された URI の正しいタイプのリソースを作成します。[18] ロード時に、文書の相互参照のデマンド・ロードも管理します。
このプログラムを実行すると、以下のような内容を持つファイル mylibrary.xmi が作成されます。
<xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:library="http:///library.ecore"> <library:Book title="King Lear" author="/1"/> <library:Writer name="William Shakespeare" books="/0"/> </xmi:XMI>
代わりに別の文書に books と writers をシリアライズしたい場合は、2 つめの文書を開くだけですみます。
Resource anotherResource = resourceSet.createResource(anotherFileURI);
そして最初のリソースの代わりに writer を追加します。
resource.getContents().add(writer);anotherResource.getContents().add(writer);
これにより、それぞれ 1 つのオブジェクトを持った、お互いに文書の相互参照ができる 2 つのリソースが作成されます。
包含参照の場合は、含まれているオブジェクトが必ずそのコンテナーと同じリソースにあることを意味することに注意してください。 たとえば、books 包含参照を介して Book を含んだ Library のインスタンスを作成したとします。 これにより Book は自動的にリソースから除去されたはずで、リソースはその意味では包含参照のように作動します。 その後で Library をリソースに追加したら、その book も必ずリソースに属することになり、またその詳細はシリアライズされます。
XMI 以外のフォーマットでオブジェクトをシリアライズしたければ、それも可能です。 その場合は独自のシリアライゼーション・コードおよび構文解析コードを提供する必要があります。 優先したいシリアライゼーション・フォーマットを実装する、ユーザーの独自のリソース・クラスを (ResourceImpl のサブクラスとして) 作成し、ご使用のリソース・セットでローカルに登録するか、あるいは、ご使用のモデルで常に使いたい場合はグローバルなファクトリーのレジストリーでローカルに登録します。
前に、生成した EMF クラスに設定されたメソッドを見た時、属性や参照が変更されるときは必ず通知が送信されていました。 たとえば、BookImpl.setPages() メソッドは以下の行を含んでいました。
eNotify(newENotificationImpl(this, ..., oldPages, pages));
各 EObject は、状態の変化が起きた場合に通知を行うオブザーバー (アダプターともいう) のリストを維持することができます。 フレームワークの eNotify() メソッドが、このリスト内のオブザーバーに繰り返して使用され、通知を転送します。
以下のように、eAdapters リストに追加することによって、オブザーバーを任意の EObject (たとえば book) に接続できます。
Adapter bookObserver = ... book.eAdapters().add(bookObserver);
しかしもっと一般的には、アダプター・ファクトリーを使ってアダプターを EObjects に追加します。 オブザーバーの役割に加え、アダプターは、より一般的にはアダプターが接続されているオブジェクトの振る舞いを拡張する方法として使用されます。 クライアントは、一般に、アダプター・ファクトリーに対して必要なタイプに拡張したオブジェクトを適用 (adapt) するよう要求することによって、拡張された振る舞いを付加します。 通常、以下のようになります。
EObject someObject = ...; AdapterFactory someAdapterFactory = ...; Object requiredType = ...; if(someAdapterFactory.isFactoryForType(requiredType)) { Adapter theAdapter = someAdapterFactory.adapt(someObject, requiredType); ... }
通常は、requiredType は、アダプターがサポートするインターフェースなどを表します。 たとえば、この引き数は、選択したアダプターのインターフェースとして実際の java.lang.Class である場合があります。 戻されたアダプターは、次に、要求されたインターフェースに以下のようにダウンキャストされることがあります。
MyAdapter theAdapter = (MyAdapter)someAdapterFactory.adapt(someObject, MyAdapter.class);
アダプターはたいていこの方法で使用され、サブクラス化を行わずにオブジェクトの振る舞いを拡張します。
アダプターの通知を処理するには、eNotifyChanged() メソッドをオーバーライドする必要がありますが、eNotifyChanged() メソッドは登録されたすべてのアダプターについて eNotify() によって呼び出されます。通常、アダプターは、eNotifyChanged() を実装して、通知のタイプに基づき、通知のいくつか、またはすべてに関するアクションを実行します。
時にはアダプターが特定のクラス (たとえば、Book クラス) を adapt するように設計されている場合もあります。 このようなケースでは、notifyChanged() メソッドは以下のようになります。
public void notifyChanged(Notification notification) { Book book = (Book)notification.getNotifier(); switch (notification.getFeatureID(Book.class)) { case LibraryPackage.BOOK__TITLE: // book title changed doSomething(); break; caseLibraryPackage.BOOK__CATEGORY: // book category changed ... case ... } }
notification.getFeatureID() への呼び出しは引き数 Book.class を渡されていて、これは adapt されるオブジェクトが BookImpl クラスのインスタントではなく、多重継承で Book が 1 次 (第 1 の) インターフェースではないサブクラスのインスタンスである場合の処理をします。その場合、通知の中で渡されるフィーチャー ID はもう一つのクラスとの相対番号となるので、BOOK__ 定数を使った switch 文の前に置く必要があります。 単一継承の場合には、この引き数は無視されます。
もう一つのよくあるタイプのアダプターはどの特定のクラスにも関係せず、代わりに reflective EMF API を使ってその機能を実行します。通知の際に getFeatureID() を呼び出すのではなく、代わりに getFeature() を呼び出し、それが実際の Ecore 機能 (すなわち、機能を表すメタモデルのオブジェクト) を戻します。
生成されたすべてのモデル・クラスはまた、インターフェース EObject で定義された reflective API を使って操作することができます。
public interface EObject ... { .. Object eGet(EStructuralFeature feature); void eSet(EStructuralFeature feature, Object newValue); boolean eIsSet(EStructuralFeature feature); void eUnset(EStructuralFeature feature); }
reflective API を使えば、以下のように作成者の名前を設定できます。
writer.eSet(LibraryPackage.eINSTANCE.getWriter_Name(), "William Shakespeare");
または、以下のようにして、名前を取得できます。
String name = (String)writer.eGet(LibraryPackage.eINSTANCE.getWriter_Name());
アクセスしているフィーチャーはライブラリー・パッケージの唯一のインスタンスから取得したメタデータによって識別されていることに注意してください。
reflective API の使用は生成された getName() および setName() メソッドを直接呼び出すよりもわずかに効率性に劣ります [19] が、完全な汎用アクセスのモデルに対応しています。 たとえば、どのモデルにも使える汎用コマンド (たとえば、AddCommand、RemoveCommand、SetCommand) をすべて実装するためには EMF.Edit フレームワークによって reflective メソッドが使用されます。詳しくは、『EMF.Edit フレームワークの概説 』を参照してください。
eGet() と eSet() のほかに、reflective API には eIsSet() と eUnset() という、2 つの関連したメソッドが含まれています。eIsSet() メソッドは、属性が設定されているかどうかを調べるために使用でき[20] 、 eUnset() は設定解除 (つまりリセット) に使用できます。たとえば、汎用 XMI シリアライザーは eIsSet() を使って、リソースの保管操作中にどの属性をシリアライズする必要があるかを決定します。
モデルのフィーチャーの生成されたコードのパターンを制御するために、フィーチャーにいくつかのフラグを設定することができます。 一般に、これらのフラグはデフォルト設定でよいので、特に頻繁に変更する必要はありません。
Unsettable (デフォルトは false)
unsettable と宣言されるフィーチャーには、設定解除または値が存在しない状態の明示的な表記があります。たとえば、unsettable ではないブール属性は、true または false のいずれかの値をとります。もし、その属性が unsettable と宣言された場合は、true、false、または unset の 3 つの値のいずれかをとることができます。
設定されていないフィーチャーでは get メソッドはデフォルト値を戻しますが、unsettable フィーチャーの場合は、この状態と、フィーチャーに明示的にデフォルト値が設定された場合の状態とでは違いがあります。 設定されていない状態は使用可能な値の範囲外にあるため、フィーチャーを設定解除状態にしたり、その状態であるかどうかを判断する追加のメソッドを生成する必要があります。 たとえば、Book クラスのページ属性を unsettable と宣言すると、さらに以下の 2 つのメソッドが生成されます。
boolean isSetPages(); void unsetPages();
これらが以下の 2 つのオリジナルのメソッドに追加されます。
int getPages(); void setPages(int value);
isSet メソッドは、フィーチャーが明示的に設定されていれば true を戻します。unset メソッドは、値が設定された属性を変更して設定されていない状態に戻します。
unsettable が false のときは isSet や unset メソッドは生成されませんが、reflective メソッドの実装である eIsSet() か eUnset() は生成されます (これはすべての EObject が実装する必要があります)。 unsettable でない属性では、eIsSet() は現在の値がデフォルト値と異なる場合に true を戻し、eUnset() はフィーチャーをデフォルト値に設定します (リセットします)。
ResolveProxies (デフォルトは true)
ResolveProxies は、非包含参照にのみ適用されます。ResolveProxies は、参照が文書間にまたがることを暗黙に意味するため、当資料で前に述べたように、get メソッドにプロキシーの検査と解決を含める必要があります。
resolveProxies を false に設定することによって、文書をまたがるシナリオでは決して使用されないことがわかっている参照に対して、生成される get パターンを最適化できます。 その場合、生成された get メソッドは最適に機能します[21] 。
Unique (デフォルトは true)
Unique は、多重度が多の属性のみに適用され、その属性に同一のオブジェクトが複数を含まれないことを示します。 参照は常にユニークなものとして処理されます。
Changeable (デフォルトは true)
changeable ではないフィーチャーには set メソッドは生成されず、reflective eSet() メソッドは、それを設定しようとすると例外をスローします。 両方向関係の一方が changeable ではないことを宣言することは、クライアントに常にもう一方の側から参照を設定することを強制し、どちらからも便利にナビゲーションできるメソッドが提供されるため良い方法です。 片方向の参照の宣言つまり属性が changeable でないと宣言することは通常、そのフィーチャーは何か他の (ユーザー作成の) コードによって設定、変更されることを暗黙指定します。
Volatile (デフォルトは false)
volatile と宣言されるフィーチャーは、保管されるフィールドが無く、実装メソッドの本体が空で生成されるので、ユーザーがコードを入力する必要があります。 volatile は一般に、値が何か別のフィーチャーから導出されるようなフィーチャーや、別のストレージおよび実装パターンを用いて手動で実装するべきフィーチャーに使用されます。
Derived (デフォルトは false)
derived フィーチャーの値は別のフィーチャーから計算されるので、追加のオブジェクト状態を表しません。 モデル・オブジェクトをコピーする EcoreUtil.Copier のようなフレームワーク・クラスは、そのようなフィーチャーをコピーすることはしません。 生成されたコードは derived フラグの値に影響されませんが、パッケージ実装クラスだけは例外で、モデルのメタデータを初期化します。 Derived フィーチャーは、一般に、volatile および transient もマークされます。
Transient (デフォルトは false)
transient フィーチャーは、その存続期間がアプリケーションの次回の起動にまたがることが決してないため、持続する必要がないデータを宣言 (モデル化) するのに使用されます。 シリアライザー (デフォルトでは XMI) は、transient と宣言されたフィーチャーを保管しません。derived と同様、生成されたコードが transient の影響を受けるのは、パッケージ実装クラスにおけるメタデータの初期化のみです。
前述したように、モデルで定義されるすべてのクラス (たとえば、Book、Writer) は、EMF ベースの EObject クラスから暗黙的に派生しています。しかし、モデルが使用するすべてのクラスが必ずしも EObject ではありません。たとえば、自分のモデルに java.util.Date タイプの属性を追加したいとします。そうする前に、外部タイプを表す EMF データ型を定義する必要があります。UML では、この目的のためにデータ型のステレオタイプを持つクラスを使用します。
上記のように、データ型は、モデル中の名前のついた単なるエレメントで、何かの Java クラスのプロキシーとして動作します。実際の Java クラスは javaclass ステレオタイプの属性として提供され、その完全修飾クラス名で表されます。 このデータ型を定義しておけば、java.util.Date タイプの属性を以下のようにして宣言できます。
再生成を行うと、publicationDate 属性が Book インターフェースに現れます。
import java.util.Date; public interface Book extends EObject { ... Date getPublicationDate(); void setPublicationDate(Date value); }
ご覧のように、この Date タイプの属性は、他の属性とほとんど同様に処理されています。 事実、String、int などのタイプをはじめとしてすべての属性は、データ型をタイプとして持っています。 ただ標準の Java タイプについて特別なことは、対応するデータ型が Ecore モデルで定義済みなので、それらのデータ型を使用するモデルで個々に再定義する必要がないということです。
データ型定義は、生成されたモデルに対して別の効果もあります。データ型は任意のクラスを表すので、汎用のシリアライザーやパーサー (たとえば、デフォルトの XMI シリアライザー) は、そのタイプの属性の状態をどのように保管すればよいか分かりません。 toString() を呼び出せばいいかもしれません。これはデフォルトとして合理的ですが、EMF フレームワークはそうはせず、モデル内で定義されるすべてのデータ型のファクトリー実装クラスにメソッドをあと 2 つ生成します。
/** * @generated */ public Date createJavaDateFromString(EDataType eDataType, String initialValue) { return (Date)super.createFromString(eDataType, initialValue); } /** * @generated */ public String convertJavaDateToString(EDataType eDataType, Object instanceValue) { return super.convertToString(eDataType, instanceValue); }
デフォルトでは、これらのメソッドはそのままスーパークラスの実装を起動しますが、これはと合理的ですが、非効率なデフォルト値を提供します。convertToString() は instanceValue の toString() を呼び出すだけですが、createFromString() は Java reflection を使用して、String コンストラクターを呼び出すか、それが失敗した場合は、もしあれば静的な valueOf() メソッドの呼び出しを試みます。 一般にこれらのメソッドは (@generated タグを削除して) 自分で手直し、カスタマイズした適切な実装に変更すべきです。
/** *@generated*/ public String convertJavaDateToString(EDataType eDataType, Object instanceValue) { return instanceValue.toString(); )
以下は、Ecore モデルの完全なクラス階層です (グレイのボックスは抽象クラス)。
ご覧のように、ここでは本資料で説明している EMF 成果物を表すクラスをいくつか紹介しています。クラス (およびその属性、レファレンス、および操作) のデータ・タイプ、列挙型、パッケージ、およびファクトリーなどです。
EMF の Ecore 実装はそれ自体 EMF 生成プログラムを使って生成されていますが、このため、本資料で前に説明したように、軽量で効率的なインプリメンテーションとなっています。
[1] 実際は EMF メタモデル自体が EMF モデルで、XMI はそのデフォルトのシリアライズされた形式です。
[2] 現在、 EMF は Rational Rose からのインポートをサポートしていますが、生成プログラムのアーキテクチャーは他のモデリング・ツールにも同様に、簡単に対応できます。
[3] EMF は JavaBean の単純なプロパティー・アクセサーのネーミング・パターンのサブセットを使用します。
[4] 生成されたパターン変更に使用可能な、幾つかのユーザー指定のオプションがあります。 そのいくつかは後述します。(詳しくは本資料後半の 『生成コントロール・フラグ』を参照してください。)
[5] 後述する包含 参照 (『包含参照 』を参照) は、文書をまたがることができません。resolve メソッドを呼び出す必要がないことをユーザーが参照のメタデータに使用することができるフラグがあります。Resolve メソッドを呼び出す必要がないのは、その参照は文書をまたがる相互参照で使用されるシナリオが考えられないからです。( 『生成コントロール・フラグ』を参照) この場合、生成された get メソッドはポインターを戻すのみとなります。
[6] リンク切れ処理を行う必要のあるアプリケーションは、get メソッドにより戻されたオブジェクトで eIsProxy() を呼び出し、それが解決されているかどうかを確認します。(例、 book.getAuthor().eIsProxy()).
[7] これは明らかに author が複数の場合に対応していませんが、モデルの例はシンプルになっています。
[8] あえて basicSet() メソッドを呼び出す理由は、もう少し後で出てくる eInverseAdd() および eInverseRemove() メソッドでも必要であるためです。
[9] 実際、具体的な EList の実装は、非常に機能的かつ効率的なベース実装クラスである EcoreEList のシンプルなサブクラスです。
[10] eInverseAdd() では、単に提供されるフィーチャー ID にスイッチするのではなく、まず eDerivedStructuralFeatureID (featureID, baseClass) を呼び出します。簡単な単一継承モデルについては、このメソッドは 2 番目の引き数を無視して渡されたフィーチャー ID を戻す、デフォルトの実装を持っています。 多重継承を使用するモデルについては、eDerivedStructuralFeatureID() は、ミックスイン・クラス (すなわち、baseClass) に相対的なフィーチャー ID を、具体的な派生クラスのインスタンスにおいて相対的なフィーチャー ID に調整するためのオーバーライドが生成される場合があります。
[11] EObjectImpl はまた、int タイプの eContainerFeatureID インスタンス変数を持ち、どのレファレンスが現在 eContainer に使われているかを追跡します。
[12] http://java.sun.com/developer/JDCTechTips/2001/tt0807.html#tip2 を参照してください。
[13] Java のプログラミング・スタイルに準拠するために、モデル化された列挙型のリテラル名がまだ大文字でない場合、静的定数名は大文字に変換されます。
[14] プログラムではファクトリー・インターフェースやパッケージ・インターフェースを使用するにあたって厳しい要件はありませんが、EMF はクライアントに対し、モデル・クラスのプロテクトされたコンストラクターを生成することによってインスタンス作成時にファクトリーを使用することを推奨しています。これにより、ユーザーは単にメソッドを呼び出すだけでインスタンスを新規作成してしまうのを回避することができます。 しかし、本当に生成されたクラスのアクセスを public に変更したい場合には、生成されたクラスを手動で変更できます。 後でクラスを再生成しようと決めた場合、ユーザーの設定は上書きされません。
[15] 実際に、Ecore モデルの最初のベース・クラスは、実装のベース・クラスで使用されたものと同じです。 UML ダイアグラムでは、Book が Ecore 表現の先頭に来るべきことを示すために、<<extend>> ステレオタイプが必要です。
[16] いくつかの機能に独自のカスタム実装を提供することがあらかじめわかっていれば、それを行うのによりよい方法は属性を volatile としてモデル化を行い、その属性は最初にスケルトンのメソッド本文を生成プログラムで作ってから、それを実装するという方法です。
[17] EMF にはメタモデル・オブジェクトに追加情報のアノテーションをつけるための汎用メカニズムがあります。 このメカニズムはユーザーの文書をモデルの要素に接続し、モデルが XML スキーマで作成される時に、EMF はこれによって Ecore では直接表現できない詳細のシリアライゼーションを取り込みます。
[18] 上記のコードは、スタンドアロンで実行した場合(すなわち、必要な EMF JAR ファイルをクラスパスにおいて JVM で直接呼び出した場合)は、デフォルトのリソース・ファクトリーでリソース・ファクトリーのレジストリーを初期化していないため、エラーになります。 以下の行をリソース・セットの作成直後に挿入してください。
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put( Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());
次に、リソース・セットはデフォルトの XMI リソースの実装を使ってシリアライゼーションを行います。 EMF が Eclipse 内で実行された場合、同じ登録が自動的にグローバル・リソース・ファクトリー・レジストリーに作成されます。
[19] reflective メソッドの実装も各モデル・クラスに生成されます。 メソッドはフィーチャー・タイプによって、適切に生成されたタイプ・セーフなメソッドを呼び出すだけです。
[20] 設定する属性の構成については、『生成コントロール・フラグ』の unsettable フラグを参照してください。
[21] そのフィーチャーがプロキシーの解決を行わないと宣言する前によく考えてください。 あなたが文書を相互参照する状況で参照を使う必要がないからといって、あなたのモデルを使いたいと考える誰かほかの人もそうだといえるわけではありません。 フィーチャーがプロキシー解決をしないと宣言するのは、Java クラスの final 宣言するのと同じようなものです。