IAdaptableってなに †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の仕組みだと、ただひとつの汎用的なアダプタでそれを実現することができます。まさにアダプタです。 この緩やかさ(疎すぎるところ?)が、またわかりにくいなぁっておもったりもするんだけど。 PlatformObject? ってなに?IAdapterFactory? てなに?? †さて、この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 例1 HashMap?をListに変換するアダプタ †極めてわかりにくい仕組みなので、ためしに以下の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です。 例2 Viewerへの表示に使用するアダプタ org.eclipse.ui.model.IWorkbenchAdapter? †Viewerでの使われ方 †Viewer系のModelと2つのプロバイダで、この機構が使われています*2。具体的にはorg.eclipse.ui.model.BaseWorkbenchContentProvider?とorg.eclipse.ui.model.WorkbenchLabelProvider? を用いたビューワへのモデルの表示ですね。 このようなディレクトリ構造になっているモデルをBaseWorkbenchContentProvider?とWorkbenchLabelProvider?を使ってTreeViewer?に表示したいと思います。トップレベルのクラスContactはPlatformObject?をextendsしています。 BaseWorkbenchContentProvider?の実装がどうなってるか †さて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したときにどの様なオブジェクトを返すのか、が重要となります。 IAdapterFactory?の実装クラス †ここでは以下のように実装しました。 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); } 関連リンク †
この記事は
現在のアクセス:16309 |