レベル: 中級 Adrian Powell, Senior Software Developer, IBM
2004年 4月 15日 Eclipse Modeling Framework(EMF)とは、モデル駆動型のアプリケーションを開発するためのオープン・ソース・フレームワークです。XMLスキーマ、UML、または注釈付きのJavaにて指定されたモデルをベースにしたデータを図形的に編集、操作、読み込み、そして直列化するJava™コードを作成します。EMFはプロジェクト(IBM® WebSphere® StudioそしてEclipse)内のツールの基礎です。この記事は、モデル作成、コード生成、生成されたアプリケーションの使用法、そしてエディターのカスタマイズを考察します。
ところで、EMFとは何でしょう?
Eclipse Modeling Framework(EMF)は、Model-Driven Architecture(MDA)開発に的を絞ったオープン・ソース・フレームワークです。UMLを入手する幸運に恵まれた人達にとっては、文書をコードに変換する手助けとなります。そうでなければ、解決法のモデル化が意味あることを無理矢理上司に説得するための道具にしか過ぎません。様々な余分な物の付属したJavaコードを生成するに付け加えて、EMFはEclipseプラグインとカスタマイズ可能なグラフィカル・エディターを生成します。モデルを変更する場合(実際にこれは頻繁に発生します)、ボタン1つでEMFはコードとモデルの間の同期を保ちます。
EMFから生成されたコードは、使い捨ての解決法ではありません。それは標準的な作成、回収、アップデート、消去の操作をサポートし、カーディナリティー制約(cardinality constraints)、複合関係、継承構造、包含の定義、そして属性記述のスイート(Suite)をもサポートします。生成されたコードは、通知、参照保全、そしてカスタマイズ可能なパーシスタンスをXMIに提供します。(当初から作成する予定だった)オブジェクト・モデルをただ作成すればよいだけなのです。
EMFは比較的歴史が浅いのですが、将来性は明るくこれからも持続的に支持されることと思われます。それは公開された基準(Object Management GroupのMeta-Object Facility (MOF))の実装であり、バージョン2の拡張をサポートします。さらに、EMFはEclipseのプロジェクト(EMF:XSDそしてHyades)の基礎を成し、大半のIBM® WebSphere® Studio製品に使用されています。バージョン2の開発は既に進行中であり、開発ビルドは直ぐに公開される予定です。そのバージョン2では、より良いXMLスキーマ・サポート、さらに柔軟性に富んだコード生成、そしてモデル間のマッピングが予定されています。
発言権はツールにあり
『客寄せ口上』はこれくらいでいいでしょう。EMFに何ができるかを確かめるためにも、コードそのものに本題を移しましょう。これらの用例は、Eclipse 3.0M7、EMF 2.0.0、そして対応するXSDツールキットを使用します。それぞれ別のバージョンのEclipse に対応する4つの独立したEMF開発の流れがありますので、Eclipseのバージョンに対応するEMFバージョンを正しく選ぶべきです。(これらのプラグインへのリンクでしたら、参考文献にあります。)
重要な特徴を表わすために、Webフォーラムの簡単な例を使用します。モデルのrootはForumで、それはMembersとTopicsのリストを含みます。それぞれのTopicはTopicCategory(列挙型タイプ)を持ち、MemberとTopicはPostクラスを介して間接的に(そしてMemberはTopicを作成できるので直接的にも)関連します。
UMLとOmondoで、EMFモデルを作成
OmondoのUMLプラグインは、Eclipse内にUML文書を作成するには、使いやすく反応の良いプラグインです。それはRational® Roseの陰に隠れたやせ細った弟分に見えますが、余分な馬力を必要としていないのであればそれで十分だと言えます。しかし運の悪いことに、それはEclipse 3をサポートしませんので、ここではUML クラス・ダイアグラムを作成するのにEclipse 2.1を使用します。
まず最初に、新規のJavaプロジェクトUMLForumそして新規のパッケージcom.ibm.example.forumを作成します。新規のEMFクラス・ダイアグラムforum.ucdを、src/com/ibm/example/forumに作成します。2つのファイル(forum.ecd と forum.ecore)が作成されます。Forumと名付けられた新規のクラスを追加し、Finishedをクリックします。図1の通り、タイプEstring(Ecoreタイプなら全ての簡単なJavaタイプにて入手可能です。)と一緒に属性descriptionをForumクラスに追加します。機能に関してはchangeableのみを選択し、バウンドを0から1に設定します。
これらの機能に関して途中で気が変われば、Propertiesビューを開き、クラスか属性を選択します。
図1. 新規Forumクラスと属性プロパティー
下記のインターフェースに対しても同様のステップを繰り返します。
|
インターフェース
|
属性
|
タイプ
| | Member | nickname |
EString
| | Topic | title |
EString
| | Post | comment |
EString
|
アソシエーションを定義するには、アソシエーションのボタンを選択し、ソース(Forum)とターゲット(Member)をクリックします。そうすれば、アソシエーションのプロパティーのダイアログが表示されます。名前をmembersに設定し、changeable と containmentのみが選択されているのを確認し、上限のバウンドを-1に設定してください。2番目のAssociation Endタブでは、Navigableの選択を外してOkをクリックします。属性名をmembersの代わりにtopicsにして、同様のことをForum とTopicにもしましょう。navigable のボックスの選択を外せば、単一方向のアソシエーションを可能にしますが、他の属性には双方向のアソシエーションを求めます。
次のようにアソシエーションを完成させましょう。
|
ソース
|
ターゲット
|
アソシエーション
|
名前
|
昨日
|
バウンド
| | Member | Topic | 最初のアソシエーション |
topicsCreated
| 変更可能 | 0から1 |
|
| 2度目のアソシエーション |
creator
| 変更可能 | 0から1 | | Topic | Post | 最初のアソシエーション |
posts
| 包含、変更可能 | 0から-1 |
|
| 2度目のアソシエーション |
topic
| 変更可能 | 0から1 | | Member | Post | 最初のアソシエーション |
posts
| 変更可能 | 0から-1 |
|
| 2度目のアソシエーション |
author
| 変更可能 | 0から1 |
異なるタイプのトピックの列挙を定義します。TopicCategoryの名前で、新規の列挙を作成してください。Literalsには、下記を追加します。
-
ANNOUNCEMENT, value = 0
-
GUEST_BOOK, value = 1
-
DISCUSSION, value = 2
それから、category(タイプ:TopicCategory)と呼ばれるTopicの新規属性を、バウンドが0から1で変更可能(changeable)と定義します。望むのであれば、プロパティー・シートの中のデフォルト値を変更できますが、ここではANNOUNCEMENTをデフォルトとして受け入れます。
図2. 完全なUMLクラス・モデル
図2に示されるとおりにUMLを完成させたあとに、EMF Modelを作成します。そうするには、新規のEMF Projectを作成し(File > New > Project... > Eclipse Modeling Framework > EMF Project)、com.ibm.example.forumをプロジェクト名に指定します。(このプロジェクト名はプラグインの名前の基礎となりますので、Eclipseプラグインの命名規則に従うことにします。)次のページにて、Load from an EMF core modelを選択してNextをクリックします。(Generatorモデル名を自動的に満たす)ecoreファイルをロードするために、ファイル・システムをブラウズしてください。最後のページでは、パッケージのとなりにあるチェック・ボックスをクリックし、Finishをクリックします。そうすれば、EMFモデル(forum.genmodel)を作成できます。それがどのように見えるか、そしてそれをどのように使用するかを知りたいのであれば、この記事の「生成されたEMFモデルを使用」へジャンプしてください。
XMLスキーマでEMFモデルを作成
XMLスキーマ(XSD)は、UMLや注釈されたJavaコードと比較してあまり表現力が豊かではありません。例えば、それは双方向の参照を表現できません。しかし、デフォルトの直列化がスキーマを使用する場合、それが直列化をカスタマイズする最も手っ取り早い方法です。特定のXML/XMIをモデルから作成したいのであれば、XSDが最良の手段と言えます。
リスト1. forum.xsdの断片
<xsd:simpleType name="TopicCategory">
<xsd:restriction base="xsd:NCName">
<xsd:enumeration value="Announcement"/>
<xsd:enumeration value="GuestBook"/>
<xsd:enumeration value="Discussion"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="Post">
<xsd:sequence>
<xsd:element name="comment" type="xsd:string"/>
<xsd:element name="author" type="xsd:anyURI" ecore:reference="forum:Member"/>
<xsd:element name="topic" type="xsd:anyURI" ecore:reference="forum:Topic"/>
</xsd:sequence>
</xsd:complexType>
|
リスト1では、列挙をどのようにして表記するかを示し、どのようにして他のタイプへの参照と要素の入ったタイプを定義するかを学べます。Forumの例では、ストリング属性「xsd:string」のみが使われていますが、全ての簡単なJavaタイプがサポートされています。XSMスキーマそしてforum.xsdに関するより詳しい情報をお求めでしたら、参考文献をご覧ください。
一度XSDを完成させれば、次にEMFモデルを作成します。UMLモデルのように、新規のEMF Project(File > New > Project... > Eclipse Modeling Framework > EMF Project)を作成してください。com.ibm.example.forumをプロジェクト名に指定します。(このプロジェクト名はプラグインの名前の基礎となりますので、Eclipseプラグインの命名規則に従うことにします。)次のページにて、Load from an XML Schemaを選択してNextをクリックします。(Generatorモデル名を自動的に満たす)XSDファイルをロードするためにファイル・システムをブラウズします。最後のページでは、パッケージのとなりにあるチェック・ボックスをクリックし、Finishをクリックします。そうすれば、EMFモデル(forum.genmodel)を作成できます。それがどのように見えるか、そしてそれをどのように使用するかを知りたいのであれば、この記事の「生成されたEMFモデルを使用」へジャンプしてください。
注釈付きのJavaコードでEMFモデルを作成
Javaコードを使用してEMFモデルを定義するには、それぞれのクラスの属性と互いの関連をリストするためにInterfacesを使用します。これは欲求する全ての情報を指定するには物足りませんので、EMFは特殊なJavaDocタグを使用します。EMFモデルの一部であるクラスまたは属性はそれぞれそのJavaDoc内に@modelタグを含み、追加属性の(オプションとしての)リストも抱えます。例えば、上記の図2にて定義されるようなオブジェクト・モデルを構築するのであれば、ここでのForumの定義はリスト2にて示されるようになります。
リスト2. 注釈付きのForum.java
package com.ibm.example.forum;
import java.util.List;
/**
*@model
*/
public interface Forum {
/**
*@model type="Topic" containment="true" */
List getTopics();
/**
*@model type="Member" containment="true" */
List getMembers();
/**
*@model */
String getDescription();
}
|
リスト2は、String記述、2つの子(Topicsのリスト、そしてMembersのリスト)と共に、Forumと呼ばれるオブジェクトを宣言します。子は両方ともにForumの中にあります。
descriptionのように単純な属性であれば、@modelタグで十分です。しかし、リストの場合には全てのタイプを特定する必要があります。containment属性はオプションですが、オブジェクトが含まれるのであれば、それはコンテナーと共に直列化されます。直列化を簡略化するには、全てのオブジェクトが直接的そして間接的にForumにより包含されるのを確認すべきです。役に立つオプションの属性の一部を、以下に示します。
-
opposite(双方向のプロパティー用)
-
default(属性のデフォルト値)
-
transient(属性は直列化されず)
完全なリストをお求めでしたら、参考文献に示されるEMFユーザー・ガイドをご覧になってください。
もう一つの注意点は、列挙タイプがClass で定義され(他のモデル・クラスの如く)Interfaceで定義されていないことです。それを明確にするには、列挙タイプTopicCategoryをどのようにして実装するかを、リスト3にて示します。
リスト3. 列挙タイプ、TopicCategory.java
package com.ibm.example.forum;
/**
* @model
*/
publicclass TopicCategory{
/**
* @model name="Announcement"
*/
public static final int ANNOUNCEMENT = 0;
/**
* @model name="GuestBook"
*/
public static final int GUEST_BOOK = 1;
/**
* @model name="Discussion"
*/
public static final int DISCUSSION = 2;
}
|
モデルを完成するには、最後の3つのインターフェースを以下のとおりに生成します。
|
インターフェース
|
メソッド
|
モデルのタグ
| | Member |
List getPosts()
| type="Post" opposite="author" |
|
List getTopicsCreated()
| type="Topic" opposite="creator" |
|
String getName()
|
| | Topic |
List getPosts()
| type="Post" opposite="author" |
|
Member getCreator()
| opposite="topicsCreated" |
|
String getTitle()
|
|
|
TopicCategory getCategory()
|
| | Post |
Member getAuthor
| opposite="posts" |
|
Topic getTopic()
| opposite="posts" |
|
String getComment()
|
|
モデルが定義されれば、EMFモデルを生成します(File > New > Other > Eclipse Modeling Framework > EMF Models)。親フォルダーをcom.ibm.example.forum/src/modelに、そしてFile nameをforum.genmodelに設定します。次のページで、Load from annotated Javaを選択し、「forum」のとなりにあるチェック・ボックスを選択します。そして、Finishをクリックします。そうすれば、EMFモデル(forum.genmodel)を作成できます。
生成されたEMFモデルを使用
これでワークスペース(forum.genmodel)にてEMFモデルが生成されました。このモデルは、モデルに入力された情報を全て含みます。デフォルトのエディター(図3参照)でモデルを開いてからPropertiesビューを開き、モデルのツリーのそれぞれのノードのプロパティーを調べましょう。以前に入力された全ての属性はカスタマイズされるかも知れませんが、コード生成をカスタマイズするプロパティーも存在します。ためしに、「Copyright Text」や「Generate Schema」のようなプロパティーを変更し、何が起きるかを観察しましょう。
図3. デフォルトのエディターで開く生成されたEMFモデル
モデル記述(UML, XSD, Annotated Java)に変更を加えた場合、Package Explorer内のモデルを右クリックしてReloadを選択し、モデルを再ロードします。そうすれば、EMFから生成されたモデルはモデル記述との同期を計れます。生成されたモデル内のプロパティーに変更を加えても、再ロード後にモデルは変化しません。
Javaコードを生成
モデル記述のできばえに満足するか、またはそれらが全て何を意味するのかを知りたいのであれば、コードを生成する時が来たと言えますでしょう。ルート・ノードを右クリックして、生成のオプション(Model、Edit、またはEditor code)のうち1つを選択します。 Generate Modelは、現行プロジェクト内でEMFモデルのJava実装を作成します。それは、以下の項目を含みます。
-
com.ibm.example.forum -- Javaクラス作成のためのインターフェースそして Factory
-
com.ibm.example.forum.impl -- com.ibm.example.forumにて定義されたインターフェースの具体的な実装
-
com.ibm.example.forum.util -- AdapterFactory
Generate Editor Codeはcom.ibm.example.forum.editプロジェクトを作成します。これは、エディター内でそれぞれのモデル・オブジェクトがどのように見えるかを制御するパッケージcom.ibm.example.forum.providerのみを含みます。 Generate Editor Codeは、サンプルのプラグイン・エディターを(com.ibm.example.forum.presentationを含む)com.ibm.example.forum.editorプロジェクトの中で作成します。モデルと相互に作用する簡単なJFace エディターをこれらのクラスは提供します。
生成されたプラグインをテストするには、Run > Run... > Run Time Workbench > Newの手順を踏んでください。記述的な名前を与え、プラグインのタブを選んでから、launch with all workspace and enabled external plug-insを選択してください。Commonのもとでは、Display in favorites menu > RunそしてLaunch in backgroundをクリックしてください。この構成を保存して、実行しましょう。
新規のEclipseワークベンチが表示され、Help > About Eclipse Platform > Plug-in Detailsでプラグインが使用可能になったことが検証できます(図4参照)。
図4. Forum関連のプラグインの詳細
生成されたプラグインをテストするには、新規の「Forum Demo」と名付けられた単純なプロジェクトを作成し、 New > Other... > Example EMF Model Creation Wizards > Forum Modelの手順を踏まえてください。それにsample.forumのファイル名を与え、Forumを Model Objectとします。そうすれば、新規モデル要素をルートに追加できるウィンドウが表示されます。Selection、Parent、List、Tree、Table、TreeTableのビューがあるのを確認してください。全てのビューが同じデータを表示し、Outlineのビューと同期しています。全てのビューがNew Sibling/New Childの右クリックメニューのオプションを可能にしているのですが、子(children)や兄弟(siblings)が追加されるとビューの一部が不適切な反応を示します。このような事態が発生すれば、TableTreeビューを使うかOutline ビューにて新規ノードを作成しましょう。図5は生成されたプラグイン・エディターを示します。
図5. 生成されたプラグイン・エディター
生成されたコードのカスタマイズ
生成されたコードは確かに素晴らしいのですが、実はそれだけだとスタートラインに立っただけにしか過ぎないのです。必要条件を満たすには、(生成されたモデル・クラスの実装の変更から、エディターのカスタマイズと拡張までに渡る)調整とカスタマイズを実行します。幸運なことに、EMFは我々の期待を裏切りません。再生成するときに変更を失わずにカスタマイズを好きなだけ行なえます。(JavaDocの)@generated タグをただ外すだけのことです。EMFのjmergeがメソッド、属性、またはクラスがいじられていないことを確認します。
可能な変更を強調するために、簡単な例を挙げましょう。生成されたエディターのTable ビューの中で、同一の値の行が2つあります。これはあまり使いやすいとは言えません。改良を加えるには、Topic が選択されたときに2つ目の行がAuthor を表示するようにして、Topic内のポストの数を示す3つ目の行を追加します。
最初のステップでは、Table ビューに行を追加します。それは、com.ibm.example.forum.editorプロジェクトにて、createPages()の中のcom.ibm.example.forum.presentation.ForumEditor で実行されます。@generated タグを外してその変化を不変にし、それからテーブル・ビューアーのセクションを探します。リスト4に示されるとおり、コードを修正します。
リスト4. 修正されたcreatePages()
TableColumn selfColumn = new TableColumn(table, SWT.NONE);
layout.addColumnData(new ColumnWeightData(2, 100, true));
selfColumn.setText("Author");
selfColumn.setResizable(true);
TableColumn numberColumn = new TableColumn(table, SWT.NONE);
layout.addColumnData(new ColumnWeightData(4, 100, true));
numberColumn.setText("Number of Posts");
numberColumn.setResizable(true);
tableViewer.setColumnProperties(new String [] {"a", "b", "c"});
|
そうすれば、行を追加できますが、3つの行全てが同じデータを表示します。それぞれの行のデータをカスタマイズするには、ITableItemLabelProvider実装を供給します。com.ibm.example.forum.provider.TopicItemProvider を開き、実装リストにITableItemLabelProvider を追加します。リスト5に示されるとおり、2つのメソッド(getColumnText(Object, int) そしてgetColumnImage(Object, int))を追加します。
リスト5. TopicItemProvider への追加
public String getColumnText(Object obj, int index) {
if( index == 0 ){
return getText(obj);
}
else if( index == 1 ) {
return ((Topic)obj).getCreator().getNickname();
} else if( index == 2 ) {
return " + ((Topic)obj).getPosts().size();
}
return "unknown";
}
public Object getColumnImage(Object obj, int index) {
return getImage( obj );
}
|
最後に、このプロバイダーを登録します。そうするには、リスト6に示されるとおり、ITableItemLabelProvider をサポートされたタイプに追加するためにcom.ibm.example.forum.provider.ForumItemProviderAdapterFactoryのコンストラクターを編集します。
リスト6. ForumItemProviderFactory コンストラクター
public ForumItemProviderAdapterFactory() {
supportedTypes.add(ITableItemLabelProvider.class);
supportedTypes.add(IStructuredItemContentProvider.class);
supportedTypes.add(ITreeItemContentProvider.class);
supportedTypes.add(IItemPropertySource.class);
supportedTypes.add(IEditingDomainItemProvider.class);
supportedTypes.add(IItemLabelProvider.class);
}
|
プラグインを実行しテーブル・ビューに移行すれば、図6のようになります。ITableItemLabelProvider を実装しない要素が同一のテキストを全ての行に表示することに注目してください。
図6. 修正されたTable エディター
Javaにてモデルを操作
生成されたモデル・コードは、普通のJava コードに役に立つ何かが追加されたかのようになります。柔軟性に富み、カスタマイズされ、ツールに取って役立つリフレクションAPIがあります。それは既にご存知の、eGet() とeSet() メソッドです。これはここではほとんど関係ありませんので、これはひとまずおいといて、より興味深い事柄(モデルの作成、保存、ロード)に注目してください。最初のステップ(EMF モデルのロード)から始めましょう。
リスト7. Forumをロード
// Register the XMI resource factory for the .forummodel extension
Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
Map m = reg.getExtensionToFactoryMap();
m.put("forummodel", new XMIResourceFactoryImpl());
ResourceSet resSet=new ResourceSetImpl();
Resource res = resSet.getResource(URI.createURI("model/forum.forummodel"),true);
Forum forum = (Forum)res.getContents().get(0);
|
どのようにして「forummodel」の拡張付きのファイルをXMIフォーマットとして関連付けし、そしてEMFのResourceSet を活用してフォーラム・モデルを構文解析そしてロードするかを、リスト7にて示します。Forum が唯一のルート要素であるのは解っておりますので、res.getContents().get(0)が唯一のForum オブジェクトを戻すのを予測できます。仮にそうならなければ、Iterator をgetContents().iterator()から摘出しそれぞれの要素を別々に検査します。
別の方法として、リスト8に示されるとおりに、新規のForum を作成しプログラム上でデータを追加する手もあります。
リスト8. Forumの初期化
// initialize model and dependencies
ForumPackageImpl.init();
// retrieve the default Forum factory singleton
ForumFactory factory = ForumFactory.eINSTANCE;
Forum forum = factory.createForum();
forum.setDescription("programmatic forum example");
Member adminMember = factory.createMember();
adminMember.setNickname("Administrator");
forum.getMembers().add( adminMember );
Topic noticeTopic = factory.createTopic();
noticeTopic.setTitle("Notices");
noticeTopic.setCategory(TopicCategory.ANNOUNCEMENT_LITERAL);
noticeTopic.setCreator(adminMember);
forum.getTopic().add( noticeTopic );
|
この例では、パッケージを初期化してから、全てのサブオブジェクトを作成するのに使われるForumFactory を作成します。一度作成されれば、それらは標準的なJavaBeanのごとくアクセスされます。しかしながら、Topic とMember の間のcreatorとtopicsCreatedの関係が双方向であることを宣言したので、noticeTopic.setCreator(adminMember)を呼び出せばadminMember のtopicsCreated のリストはnoticeTopicを含むことになります。
EMF モデルを一度作成し操作すれば、あとは選んだフォーマットにそれ(Forum)を保存するだけです(リスト9参照)。
リスト9. Forumを保存
URI fileURI = URI.createFileURI("model/forum.ecore");
Resource resource = new XMIResourceFactoryImpl().createResource(fileURI);
resource.getContents().add( forum );
try {
resource.save(Collections.EMPTY_MAP);
} catch (IOException e) {
e.printStackTrace();
}
|
この例では、URI.createFileURI() とターゲット・フォーマットを使いセーブ先のファイル名をあてました。ここでは、XMI にセーブするために、XMIResourceFactoryImplを使います。これが一度作成されれば、パーシストしたい全てのモデル・オブジェクトを追加します。この場合、Forumを除き全てのオブジェクトが他のクラスに含まれるので、その全ての子を得るためにこれ(オブジェクト)をルートに追加するだけの話です。contains の関係を抱かぬ一部のオブジェクトは、resource.getContents().add()を介して明確に追加されるべきです。そうしなければ、resource.save()を呼び出すときに例外が生じます。
まとめ
Eclipse Modeling Frameworkは、モデル主導の開発にツールを提供します。それに含まれる要素のおかげで、開発は(実装の詳細では無く)モデルに焦点を合わせられます。主な焦点は、(カスタマイズ、通知、参照の統合性、そして他の重要な機能をサポートする)モデルの生成、カスタマイズ化可能なモデル・エディターの生成、そしてデフォルトの直列化です。用例で示されたとおり、生成は単純明快かつ単刀直入で、全てのカスタマイズされたコードはカスタマイズ化をサポートします。(直列化エディターやグラフィカル・エディターのような)個々のツールは引き出され単独で使用されますが、全ての分野が協力して使われれば全力を発揮します。既に、EMF は多くの成功したプロジェクトで使用され、成長を続けています。
参考文献
著者について  | |  | Adrian Powell は、VisualAge for Java Enterprise Tooling のチーム(そこではコード生成プログラムを2年間に渡り闇雲に手書きした)に参加したときに、IBM でのJava ツール関連の仕事を始めました。それ以降、Eclipse とVisualAge for Javaのほとんど全てのバージョンにツールとプラグインを開発しました。Adrian Powell はVancouver Centre for IBM e-Business Innovationにて勤務中で、そこでは彼の代わりとして働くプログラムを作成しています。 |
記事の評価
|