原文:http://www.eclipsezone.com/articles/what-is-iadaptable/

这里是翻译:http://bjzhanghao.cnblogs.com/archive/2005/09/24/243312.html

IAdaptable is a key interface in the Eclipse arsenal. For old hands, its use trips off the fingers like exception handling or abstract classes. For the uninitiated, it may be quite scary. This article looks at what the IAdaptable interface is, and how it can be used to extend existing functionality within Eclipse.

Casting to and from types

Since Java is a strongly typed language, each instance has a type associated with it. There are actually two sorts of type; the declared type and the runtime type (also known as the static type and dynamic type respectively). Weakly typed languages like Python are often called "untyped", but that's not strictly true; every instance has a runtime type -- you just don't need to declare it before use.

Back to Java; in order to invoke a method on an object, it needs to be visible at the declared type. In other words, you can only invoke methods that are defined in the parent type, even if the instance is of a known subtype:

 

List list = new ArrayList();
list.add("data");       // this is OK, list is valid
list.ensureCapacity(4); // this is not, ensureCapacity() is ArrayList only,// declared type决定了对象的可见方法

If we need to invoke methods on the actual type, we first need to cast it to the appropriate type. In this case, we can cast ArrayList to List, because the ArrayList type implements the List interface. You can even test for this dynamically at runtime using:

 list instanceof ArrayList.

如果想调用真正类型,而非定义类型上的方法,必须要进行类型转换到真正的类型

Extensible interfaces

Unfortunately, a class doesn't always implement the interface you need. It might not implement it because there are only some cases where it is valid, or because it is a type in an unrelated library, or because the interface was developed after the original class was written.

This is where IAdaptable comes in. You can think of IAdaptable as a way of performing casts dynamically, rather than statically. So instead of using direct casting:(动态的类型转换)

Object o = new ArrayList();
List list = (List)o;

we could do something like:

IAdaptable adaptable = new ArrayList();
List list = (List)adaptable.getAdapter(java.util.List.class);

You can read this as a dynamic cast; what we're trying to do is convert adaptable into a List instance.

So why bother with the extra step of getAdapter() when you can just use the cast directly? Well, this mechanism allows us to cast even when the target class doesn't implement the interface. For example, we might want to be able to use a HashMap as a List, despite the two otherwise incompatible classes. We could have:

IAdaptable adaptable = new HashMap();
List list = (List)adaptable.getAdapter(java.util.List.class);

Implementing IAdaptable

Most implementations of IAdaptable look like a nested if statement for supported classes. If we were to implement getAdapter() for our HashMap class, it might look like:

public class HashMap implements IAdaptable {
  public Object getAdapter(Class clazz) {
    if (clazz == java.util.List.class) {
      List list = new ArrayList(this.size());
      list.addAll(this.values());
      return list;
    }
    return null;
  }
  // ...
}
// 在职责分配上,由被转换的对象来确定如何进行转换,也只有该对象知道如何转换,被转换对象使用了本身的资源来完成转换对象的创建

What we're doing is returning an adapter to ourselves (or more specifically in this case, a copy), rather than doing the cast directly to the type. If the request is for a non-supported class, the convention is to return null to indiciate failure, rather than throwing an exception. (Thus, when using adapters, it's not generally safe to assume they will return non-null value.)

PlatformObject

Of course, it would be a pain to have to edit the class when you want to add a new 'adaptable' type; and in any case, if you've got the type, why not modify the interface? Well, there may be good reasons why you don't want to modify the class (it's less easy to support backwardly compatible changes if you're using interfaces) or change its type (a HashMap is not a List, but can be conveted into one).

当然,当需要添加新的转换类型的时候,仍然会存在修改代码的问题

To solve this problem, there's an abstract class that is used by most parts of Eclipse called PlatformObject. This implements the IAdaptable interface on your behalf, without you needing to know about it. Fine, so it implements this interface, but what good is it on its own?

It turns out that the PlatformObject delegates all of its requests to getAdapter() to something called IAdapterManager. One is provided by default for the Platform, and is accesed by calling Platform.getAdapterManager(). You can think of this as a giant Map that associates classes with appropriate adapters, and the PlatformObject's getAdapter() method as a lookup into this Map.

PlatformObject 维护了被转换类与及其支持的转换器的关联

Adapting an existing class

The net effect is that it's possible for any PlatformObject to have a new adapter type dynamically associated with it with no recompilation necessary. This is used in many places to support extensions in the Eclipse workspace.

Let's say that we want to introduce a way of converting a List of Strings into an XML node. We'd like the XML to look like:

<List>
  <Entry>First String</Entry>
  <Entry>Second String</Entry>
  <Entry>Third String</Entry>
</List>

We can't use the List's toString method, since it may be used for other purposes. Instead, we can attach a factory to the List so that when a request for an XML node is requested, a Node is automatically generated and returned.

So, how do we attach the factory? We need to do three things:

1. Generate a Node from a List (the factory)

We use the IAdapterFactory to wrap our conversion mechanism:

import nu.xom.*;
public class NodeListFactory implements IAdapterFactory {
  /** The supported types that we can adapt to */
  private static final Class[] types = {
    Node.class,
  };
  public Class[] getAdapterList() {
    return types;
  }
  /** The actual conversion to a Node */
  public Object getAdapter(Object list, Class clazz) {
    if (clazz == Node.class && list instanceof List) {
      Element root = new Element("List");
      Iterator it = list.iterator();
      while(it.hasNext()) {
        Element item = new Element("Entry");
        item.appendChild(it.next().toString());
        root.appendChild(item);
      }
      return root;
    } else {
      return null;
    }
  }
}
// 这个工厂类完成了类型的转换,这样即形成了Adapable--AdapterManager-IAdapterFactory--
2. Register the factory with the Platform's AdapterManager

We need to pass our factory into the adapter manager, so that when we ask any List instance for a Node, it knows to use our factory. The Platform looks after the IAdapterManager for us, and it's a relatively simple registration call:

Platform.getAdapterManager().registerAdapters(
  new NodeListFactory(), List.class);

This asks the platform manager to associate the NodeListFactory with the List type. When we ask for an adapter from List instances, it will consult the factory. It knows that it can obtain a Node using this factory, because that's what we've defined on the return type in the getAdapterList() method. In Eclipse, this step is normally performed explicitly at plugin startup, but it can also be done implicitly via the org.eclipse.core.runtime.adapters extension point.

3. Ask the List for a Node

This is simply a case of asking the adapter to give us a Node object:

Node getNodeFrom(IAdaptable list) {
  Object adaptable = list.getAdapter(Node.class);
  if (adaptable != null) {
    Node node = (Node)adaptable;
    return node;
  }
  return null;
}

Summary

If you want to be able to add functionality to an existing class at run-time, all we need to do is define a factory that performs the translation on an instance, and then register that factory with the Platform's AdapterManager. This functionality is great for registering UI-specific components with non-UI components whilst maintaining a clean seperation between the two, such as used by org.rcpapps.rcpnews.ui and org.rcpapps.rcpnews plugins. In these examples, IPropertySource in the UI plugin needs to be associated with the data objects in the non-UI plugin. When the UI plugin is initialised, it registers the IPropertySource with the Platform, so that when a data object is selected in the navigator, the correct properties are displayed in the properties view.

Obviously, java.util.List doesn't extend PlatformObject, so if you want the examples to compile here, you'll need to create a subtype of List to achieve the effect. However, extending PlatformObject isn't a requirement:

public class AdaptableList implements IAdaptable, List {
  public Object getAdapter(Class adapter) {
     return Platform.getAdapterManager().getAdapter(this, adapter);
  }
  private List delegate = new ArrayList();
  public int size() {
    return delegate.size();
  }
  // ...
}

The code sample uses the XOM libraries for generating the XML.