Eclipseプラグイン開発をする上で、避けて通ることができないのが、org.eclipse.core.runtime.IAdaptable インタフェースです。このインタフェースは
public Object getAdapter(Class adapter);
というメソッドだけが宣言されています。このメソッドはEclipseプラットフォーム側が、このインタフェースを実装したオブジェクトに対して「Class adapterに対応したモノ持ってる?もってたらちょうだい。。」てのを実現するためのメソッドとでもいえばよいでしょうか。
たとえばEclipse/プラグイン開発のTIPS集/プロパティシートを使うなどではビューやエディタに対して
public Object getAdapter(Class adapter) {
if(adapter.equals(IPropertySheetPage.class))
return new PropertySheetPage();
return super.getAdapter(adapter);
}
などとやって、このビューはプロパティシートを使えるよー、なんてことを指定することができるという仕組みです。普通フレームワークを作る場合、プロパティシートを使えるか、○○を使えるか、などのさまざまなインタフェースを定義して、それを実装するみたいな感じになりますが、EclipseのAdapterの仕組みだと、ただひとつの汎用的なアダプタでそれを実現することができます。まさにアダプタです。
この緩やかさ(疎すぎるところ?)が、またわかりにくいなぁっておもったりもするんだけど。
さて、このIAdaptableインタフェースですが、すべてのClass adapterに対して返すモノを考えなくてはいけないのはしんどいので、デフォルトの実装としてorg.eclipse.core.runtime.PlatformObject があるようです。でさらにそれに指示を与えるためのファクトリとしてorg.eclipse.core.runtime.IAdapterFactory があります。つまりEclipseのIAdaptableの機構を理解するには
org.eclipse.core.runtime.IAdaptable インタフェース org.eclipse.core.runtime.PlatformObject オブジェクト org.eclipse.core.runtime.IAdapterFactory ファクトリ
を理解する必要があるみたいですね。
org.eclipse.core.runtime.PlatformObjectはgetAdapterのデフォルト実装を提供するクラスです。org.eclipse.core.runtime.IAdapterFactory というファクトリは、IAdapterManagerというアダプタの管理をするマネージャに登録(regist)されて管理され、PlatformObjectのgetAdapterが呼び出されたときに、適切なオブジェクトを返すためのファクトリとなっています。
詳しく見てみます。まずPlatformObjectのgetAdapter(Class adapter)の挙動ですが、PlatformObject#getAdapter(Class adapter) ;の実装を見てみると、
public Object getAdapter(Class adapter) {
return InternalPlatform.getDefault().getAdapterManager().getAdapter(this, adapter);
} ↑IAdapterManager 型
なので、つまりPlatformObjectのインスタンスをthisとしたとき、
this#getAdapter(Class adapter);
とするのは
IAdapterManager#getAdapter(this, adapter);
とするのと等価であることがわかります。さらにIAdapterManager#getAdapterを調べてみると、
public Object getAdapter(Object adaptable, Class adapterType) {
IAdapterFactory factory = (IAdapterFactory) getFactories(adaptable.getClass())
.get(adapterType.getName());
return factory.getAdapter(adaptable, adapterType);
}(多少省略)
とthisのClass型(adaptable.getClass())とともに登録されたIAdapterFactoryを探して、そのgetAdapter(adaptable, adapterType);を呼ぶ、となっています。ファクトリの登録というのは、
IAdapterFactory factory = new ListAdapterFactory(); Platform.getAdapterManager().registerAdapters(factory, MyHashMap.class);
というようにクラスの型クラスをキーに、IAdapterManagerに登録され管理されます。
この例ではregistのメソッドでMyHashMap.classをキーにfactoryを登録しています。これはすなわちMyHashMapのインスタンスのgetAdapterを呼んだときにfactoryのgetAdapterを呼べ、ということになりますが、MyHashMapのサブクラスのgetAdapterを呼んだときもこのfactoryが稼動します。*1
極めてわかりにくい仕組みなので、ためしに以下のIAdaptableなクラス
class MyHashMap implements IAdaptable {
private Map delegate = new HashMap();
public int size() {return delegate.size();}
public Collection values() {return delegate.values();}
public Object getAdapter(Class clazz) {
if (clazz == List.class) {
List list = new ArrayList(delegate.size());
list.addAll(delegate.values());
return list;
}
return null;
}
}
を上の3つのクラスで実現してみたいと思います。ちなみにこのクラスはListくれーてEclipseがいうとListを返すHashMapみたいなもんです。つまり
MyHashMap map = new MyHashMap(); List list = (List)map.getAdapter(List.class);
なんてことができます。まさにアダプタですね。
さて、上のクラスを3つのクラスの機構で実現しようと思います。まず上のクラスをPlatformObjectを拡張するよう変更します。
// class MyHashMap implements IAdaptable {
class MyHashMap extends PlatformObject {
private Map delegate = new HashMap();
public int size() {return delegate.size();}
public Collection values() {return delegate.values();}
// ここまでは同じ
// ↓のロジックはファクトリに移動するのでコメントアウト
// public Object getAdapter(Class clazz) {
// if (clazz == List.class) {
// List list = new ArrayList(delegate.size());
// list.addAll(delegate.values());
// return list;
// }
// return null;
// }
}
つぎに、IAdapterFactoryを実装する、ListAdapterFactoryを作成します。
public class ListAdapterFactory implements IAdapterFactory {
public Object getAdapter(Object adaptableObject, Class adapterType) {
if (adapterType == List.class && adaptableObject instanceof MyHashMap) {
// 欲しがってるものがListで、MyHashMapに対してのgetAdapterだったら
// 以下の処理でListを作って返す
MyHashMap map = (MyHashMap) adaptableObject;
List list = new ArrayList(map.size());
list.addAll(map.values());
return list;
}
return null;
}
public Class[] getAdapterList() {
return new Class[] { List.class };
}
}
以上で完成です。あとは使うときに、このファクトリをEclipseプラットフォームに登録します。具体的には以下の通りです。
IAdapterFactory factory = new ListAdapterFactory(); Platform.getAdapterManager().registerAdapters(factory, MyHashMap.class);
これで登録ができました。以上で
MyHashMap map = new MyHashMap(); List list= (List)map.getAdapter(List.class);
とすることができるようになりました。
......この例って、意味ある??使い方は何となくわかったけど、定義している場所が違うだけみたいな。。。。強いて言えば、動的にアダプタを切り替えたり、実装をこのファクトリに集約することができるとか??ちなみにプラットフォームから登録を解除するには
Platform.getAdapterManager().unregisterAdapters(factory);
でOKです。
Viewer系のModelと2つのプロバイダで、この機構が使われています*2。具体的にはorg.eclipse.ui.model.BaseWorkbenchContentProviderとorg.eclipse.ui.model.WorkbenchLabelProvider を用いたビューワへのモデルの表示ですね。
このようなディレクトリ構造になっているモデルをBaseWorkbenchContentProviderとWorkbenchLabelProviderを使ってTreeViewerに表示したいと思います。トップレベルのクラスContactはPlatformObjectをextendsしています。
さてBaseWorkbenchContentProviderの実装を調べてみました。たとえば
public Object[] getChildren(Object element);
をみてみると、elementオブジェクトがIAdaptableの場合、
IAdaptable adaptable = (IAdaptable) element; IWorkbenchAdapter adapter = (IWorkbenchAdapter) adaptable.getAdapter(IWorkbenchAdapter.class); return adapter.getChildren(element);
というように、IAdaptable に castしてIWorkbenchAdapter をgetAdapter()でゲットして、IWorkbenchAdapter#getChildren(Object element) を呼ぶ、という実装になってます。つまりモデルとなるelementがどのようなIWorkbenchAdapterを返すか、で挙動が決まるって事ですね。
今回はモデルオブジェクトのelementがContactクラス(の拡張クラス)なのでIAdaptableかつPlatformObjectですので、IWorkbenchAdapterを引数にgetAdapterしたときの返り値がどうなるかは、Eclipseプラットフォームに対して、Contact.classとともに登録されたIAdapterFactoryの実装クラスが、IWorkbenchAdapterを引数にgetAdapterしたときにどの様なオブジェクトを返すのか、が重要となります。
ここでは以下のように実装しました。
package org.eclipsercp.hyperbola.model;
import org.eclipse.core.runtime.IAdapterFactory;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.model.IWorkbenchAdapter;
public class HyperbolaAdapterFactory implements IAdapterFactory {
↓ IAdapterFactory とともにregistされたContact.classのgetAdapter(adapterType)
↓ がよばれたとき、呼び出される。Contact.classのサブクラスにも有効なので、
↓ Object adaptableObjectはContactsGroupだったりContactsEntryだったりする。
public Object getAdapter(Object adaptableObject, Class adapterType) {
if (adapterType == IWorkbenchAdapter.class
&& adaptableObject instanceof ContactsGroup)
return groupAdapter;
if (adapterType == IWorkbenchAdapter.class
&& adaptableObject instanceof ContactsEntry)
return entryAdapter;
return null;
}
public Class[] getAdapterList() {
return new Class[] { IWorkbenchAdapter.class };
}
private IWorkbenchAdapter groupAdapter = new IWorkbenchAdapter() {
public Object getParent(Object o) {
return ((ContactsGroup) o).getParent();
}
public Object[] getChildren(Object o) {
return ((ContactsGroup) o).getEntries();
}
public String getLabel(Object o) {
ContactsGroup group = ((ContactsGroup) o);
return group.getName();
}
public ImageDescriptor getImageDescriptor(Object object) {return null;}
};
private IWorkbenchAdapter entryAdapter = new IWorkbenchAdapter() {
public Object getParent(Object o) {
return ((ContactsEntry) o).getParent();
}
public Object[] getChildren(Object o) {
return new Object[0];
}
public String getLabel(Object o) {
ContactsEntry entry = ((ContactsEntry) o);
return entry.getNickname() + " (" + entry.getName() + "@"
+ entry.getServer() + ")";
}
public ImageDescriptor getImageDescriptor(Object object) {return null;}
};
}
このファクトリを
Platform.getAdapterManager().registerAdapters(adapterFactory,Contact.class);
と Contact.classに登録することで、以下のことができたことになります。
なるほど、各IAdaptableなオブジェクトごとに書くよりかは、簡略化された、、、、かなあ???わかりにくいよねぇ。まだ理解できてないねえ。。
ちなみに、Viewを破棄するときにはunregisterするのを忘れないようにしましょう。
public void dispose() {
super.dispose();
Platform.getAdapterManager().unregisterAdapters(adapterFactory);
}
この記事は
現在のアクセス:16759