// 下階層用テンプレート
#topicpath
----
#contents
//ここにコンテンツを記述します。
**IAdaptableってなに [#e29fa6f9]

Eclipseプラグイン開発をする上で、避けて通ることができないのが、[[org.eclipse.core.runtime.IAdaptable:http://help.eclipse.org/help31/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/core/runtime/IAdaptable.html]] インタフェースです。このインタフェースは
 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 てなに?? [#ia7bfa63]


さて、このIAdaptableインタフェースですが、すべてのClass adapterに対して返すモノを考えなくてはいけないのはしんどいので、デフォルトの実装として[[org.eclipse.core.runtime.PlatformObject:http://help.eclipse.org/help31/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/core/runtime/PlatformObject.html]] があるようです。でさらにそれに指示を与えるためのファクトリとして[[org.eclipse.core.runtime.IAdapterFactory:http://help.eclipse.org/help31/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/core/runtime/IAdapterFactory.html]] があります。つまり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 HashMapをListに変換するアダプタ [#zb5cbeac]
極めてわかりにくい仕組みなので、ためしに以下の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[#r6a0a2c1]

***Viewerでの使われ方 [#pe086929]
Viewer系のModelと2つのプロバイダで、この機構が使われています((そもそもこれを理解するために始めたんです))。具体的にはorg.eclipse.ui.model.BaseWorkbenchContentProviderとorg.eclipse.ui.model.WorkbenchLabelProvider を用いたビューワへのモデルの表示ですね。


#ref(EA46.png)

このようなディレクトリ構造になっているモデルをBaseWorkbenchContentProviderとWorkbenchLabelProviderを使ってTreeViewerに表示したいと思います。トップレベルのクラスContactはPlatformObjectをextendsしています。


***BaseWorkbenchContentProviderの実装がどうなってるか [#b1e4e77f]
さて[[BaseWorkbenchContentProviderの実装:http://kickjava.com/src/org/eclipse/ui/model/BaseWorkbenchContentProvider.java.htm]]を調べてみました。たとえば
 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の実装クラス [#cb0e9618]
ここでは以下のように実装しました。
 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;}
   };
 }



***ここまでのまとめ [#kd61337a]
このファクトリを
 Platform.getAdapterManager().registerAdapters(adapterFactory,Contact.class);
と Contact.classに登録することで、以下のことができたことになります。

-Contact.classやそのサブクラス(ContactsGroup、ContactsEntry)のgetAdapter(Class adapterType)が呼ばれたときは、このファクトリが起動して、adapterFactory#getAdapter(Object adaptable, Class adapterType)が呼ばれます。((adaptableはContact.classなどのインスタンス))
-このファクトリにより、Contact((と他のサブクラス))#getAdapter(IWorkbenchAdapter.class)に対しては、ContactsGroupならgroupAdapter、ContactsEntryならentryAdapter、Contactの場合はnull、なIWorkbenchAdapterオブジェクトを返却します。




なるほど、各IAdaptableなオブジェクトごとに書くよりかは、簡略化された、、、、かなあ???わかりにくいよねぇ。まだ理解できてないねえ。。

ちなみに、Viewを破棄するときにはunregisterするのを忘れないようにしましょう。
 public void dispose() {
   super.dispose();
   Platform.getAdapterManager().unregisterAdapters(adapterFactory);
 }











**関連リンク [#eda3e973]
-[[What is IAdaptable?:http://www.eclipsezone.com/articles/what-is-iadaptable/]]

#ls2(Eclipse/プラグイン開発のTIPS集/org.eclipse.core.runtime.IAdaptable)



----
この記事は
#vote(おもしろかった[8],そうでもない[1])
-結局、対象となるオブジェクトがPlatformObjectとかIAdaptableとかを拡張してないとダメですね。誰かが作ったクラス、修正が不可能なクラスに対しての解がどっかにあるはず。。。[[org.eclipse.core.runtime.adapters>Eclipse/プラグイン開発のTIPS集/org.eclipse.core.runtime.IAdaptable(cont.)]]拡張ポイントあたりだな、きっと -- [[きの]] &new{2006-03-27 14:24:03 (月)};


#comment
#topicpath


SIZE(10){現在のアクセス:&counter;}



トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS