Top / Eclipse / プラグイン開発のTIPS集 / org.eclipse.core.runtime.IAdaptable

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? を用いたビューワへのモデルの表示ですね。

EA46.png

このようなディレクトリ構造になっているモデルを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に登録することで、以下のことができたことになります。

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

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

ちなみに、Viewを破棄するときにはunregisterするのを忘れないようにしましょう。

public void dispose() {
  super.dispose();
  Platform.getAdapterManager().unregisterAdapters(adapterFactory);
}

関連リンク


この記事は

選択肢 投票
おもしろかった 9  
そうでもない 1  
  • 結局、対象となるオブジェクトがPlatformObject?とかIAdaptableとかを拡張してないとダメですね。誰かが作ったクラス、修正が不可能なクラスに対しての解がどっかにあるはず。。。org.eclipse.core.runtime.adapters拡張ポイントあたりだな、きっと -- きの? 2006-03-27 14:24:03 (月)

Top / Eclipse / プラグイン開発のTIPS集 / org.eclipse.core.runtime.IAdaptable

現在のアクセス:14435


*1 サブクラスにも効くんですねこれ。非常に重要かも
*2 そもそもこれを理解するために始めたんです
*3 adaptableはContact.classなどのインスタンス
*4 と他のサブクラス

添付ファイル: fileEA46.png 1033件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2011-04-03 (日) 07:18:44 (3152d)