blob: 1758f0af7aaf75d7e7b32c43d64e6474ebce4b87 [file] [log] [blame]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Adapters</title>
<meta http-equiv="Content-Type"
content="text/html; charset=windows-1252">
<link href="../article.css" type="text/css" rel="stylesheet">
</head>
<body>
<h1>Adapters</h1>
<div class="summary">
<h2>Summary</h2>
<p>The adapter pattern is used extensively in Eclipse. The use of
this pattern allows plug-ins to be loosely coupled, yet still be tightly
integrated in the extremely dynamic Eclipse runtime environment. In this
article, we show you how to use the adapter framework to make your own
objects adaptable, and adapt other objects.</p>
<div class="author">By Wayne Beaton, The Eclipse Foundation</div>
<div class="copyright">Copyright &copy; 2008 The Eclipse
Foundation.</div>
<div class="date">June 9, 2008</div>
</div>
<div class="content">
<h2>Using the Properties View</h2>
<p>This article is not intended to provide thorough coverage of the
Properties view. Rather, the Properties view is used a a means for
demonstrating how adapters can be put to work.</p>
<p>The Properties view displays properties for whatever is selected
in the workbench. Selections can occur in many places: the Navigator and
Package Explorer views are two very obvious sources of selection. But
selections can come from other places, even views created by you and
your fellow developers. It's easy to see the Properties view in action.
Just open it and select something in the Package Explorer; the
Properties view will update to reflect the properties for the selected
thing. Here, we see the Properties view (bottom) showing properties for
file selected in the Package Explorer:</p>
<div class="figure"><img src="images/properties.png" /></div>
<p>One of the really interesting things about the Properties view is
that it can display properties for objects that it knows nothing about.
In fact, the Properties view doesn't really know anything in particular
about any of the objects it displays.</p>
<p>You can use the Properties view to show information about the objects
selected in a view that you've created. The first step is to make sure that the workbench selection
service knows about the selected in your view. If you're using a
JFace <code>TableViewer</code> or <code>TreeViewer</code>, then this
step is easy (if you're doing something else, <a
href="http://www.eclipse.org/articles/Article-WorkbenchSelections/article.html">&quot;Eclipse
Workbench: Using the Selection Service&quot;</a> by Marc R. Hoffmann will
help you determine how to best contribute the selection). You need
to do is invoke the <code>setSelectionProvider</code> method in your
view's <code>createPartControl</code> method (the important part is
marked in bold):</p>
<pre>...
public void createPartControl(Composite parent) {
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
viewer.setContentProvider(new ViewContentProvider());
viewer.setLabelProvider(new ViewLabelProvider());
<strong>getSite().setSelectionProvider(viewer);</strong>
viewer.setInput(getViewSite());
}
...</pre>
<p>Once you have your view contributing to the workbench selection,
you need to make sure that the objects that your view is selecting
contribute properties. The easiest (but not necessary most correct) way
to do this is to have your class implement the <code>IPropertySource</code>
interface:</p>
<pre>...
public class Person <strong>implements IPropertySource</strong> {
private String name;
private Object street;
private Object city;
public Person(String name) {
this.name = name;
this.street = "";
this.city = "";
}
public Object getEditableValue() {
return this;
}
public IPropertyDescriptor[] getPropertyDescriptors() {
return new IPropertyDescriptor[] {
<strong>new TextPropertyDescriptor("name", "Name")</strong>,
new TextPropertyDescriptor("street", "Street"),
new TextPropertyDescriptor("city", "City")
};
}
public Object getPropertyValue(Object id) {
if ("name".equals(id)) return name;
else if ("street".equals(id)) return street;
else if ("city".equals(id)) return city;
return null;
}
public void setPropertyValue(Object id, Object value) {
if ("name".equals(id)) name = (String)value;
else if ("street".equals(id)) street = (String)value;
else if ("city".equals(id)) city = (String)value;
}
public boolean isPropertySet(Object id) {
return false;
}
public void resetPropertyValue(Object id) {
}
}</pre>
<p>In this example, the object has three properties that are all <em>text</em>
values (one of the property descriptors that defines the behaviour of
the property in the Property view is marked in bold). The first
parameter is the name of the property, and the second one is the label
for that property in the view. There are other types of property
descriptors that you can use; you can even make your own if you have a
special type of property.</p>
<p>This example has been kept deliberately simple. These properties
cannot be reset, nor does the implementation have any notion of whether
or not properties have been set. A more sophisticated implementation
will provide the user with more sophisticated options.</p>
<p>As indicated earlier, this solution is "not necessarily <em>[the]</em>
most correct". This is because, for this to work, the domain object
needs to know about the very view-centric (and Eclipse-centric) notion
of being a property source; in short, there is a tight-coupling between
the model and the property viewing framework. This where adapters come
in.</p>
<h2>Introducing Adapters</h2>
<p>Tight coupling is bad because it tends to make things less
flexible. Sure, we can look at the properties of our domain object, but
what happens when we want to participate in other interactions? Do we
just implement another interface? And another? Tight coupling makes
reuse harder as well. Tightly coupling our domain class with the <code>IPropertySource</code>
interface makes it so that our domain class can't exist without that
interface (and all the other types packaged along with it, plus those
bits referenced by all those types, ...).</p>
<p>Eclipse provides an adapter framework that can be used to solve
this problem by decoupling the domain class from the view-specific code
required to make the Properties view work.</p>
<p>The first step is to remove the <code>IPropertySource</code>
behaviour from the domain class:</p>
<pre>...
public class Person implements <strong>IAdaptable</strong> {
private String name;
private Object street;
private Object city;
public Person(String name) {
this.name = name;
this.street = "";
this.city = "";
}
<strong>public Object getAdapter(Class adapter)</strong> {
if (adapter == IPropertySource.class) return new PersonPropertySource(this);
return null;
}
// Getter and setter methods follow...
...
}</pre>
<p>We move the <code>IPropertySource</code> behaviour to the <code>PersonPropertySource</code>
class:</p>
<pre>...
public class PersonPropertySource implements IPropertySource {
private final Person person;
public PersonPropertySource(Person person) {
this.person = person;
}
public Object getEditableValue() {
return this;
}
public IPropertyDescriptor[] getPropertyDescriptors() {
return new IPropertyDescriptor[] {
new TextPropertyDescriptor("name", "Name"),
new TextPropertyDescriptor("street", "Street"),
new TextPropertyDescriptor("city", "City")
};
}
public Object getPropertyValue(Object id) {
if ("name".equals(id)) return person.getName();
else if ("street".equals(id)) return person.getStreet();
else if ("city".equals(id)) return person.getCity();
return null;
}
public boolean isPropertySet(Object id) {
return false;
}
public void resetPropertyValue(Object id) {
}
public void setPropertyValue(Object id, Object value) {
if ("name".equals(id)) person.setName((String)value);
else if ("street".equals(id)) person.setStreet((String)value);
else if ("city".equals(id)) person.setCity((String)value);
}
}</pre>
<p>The Properties view goes through a few steps to sort out how it's
going to display properties. First, it determines whether or not the
selected object implements the <code>IPropertySource</code> interface.
If it does (as it does in our first example), it uses the selected
object directly (after casting it to <code>IPropertySource</code>). If
that check fails, the Property view then determines whether or not the
selected object implements the <code>IAdaptable</code> interface
(highlighted in the <code>Person</code> class). If the selected object
is adaptable, it is asked&mdash;via the <code>getAdapter</code>
method&mdash;for an adapter with the <code>IPropertySource</code> type.
The <code>getAdapter</code> method either returns an object of the
appropriate type or <code>null</code> if it cannot be adapted to the requested type.
If the method returns an adapter,
it is used by the Property view to gather properties (if it is <code>null</code>
the Property view shows nothing).</p>
<p>The astute reader will notice that this really doesn't do very
much to actually weaken the coupling between the domain class and <code>IPropertySource</code>
(the <code>IPropertySource</code> type is referenced directly by the <code>getAdapter</code>
method). In fact, the coupling is just as strong. Even worse, we've
actually introduced a tight coupling to another type (<code>IAdaptable</code>).</p>
<h2>Decoupling with Adapters</h2>
<p>The next step is to completely decouple the domain class from <code>IPropertySource</code>.
To do this, we change the <code>getAdapter</code> method:</p>
<pre>public class Person implements IAdaptable {
private String name;
private Object street;
private Object city;
public Person(String name) {
this.name = name;
this.street = "";
this.city = "";
}
<strong>public Object getAdapter(Class adapter) {
return AdapterManager.getDefault().getAdapter(this, adapter);
}</strong>
...
}</pre>
<p>Previously, the <code>getAdapter</code> method checked the type
of the desired adapter and created an appropriate instance (if possible)
itself. Now, the method makes a call to the <code>AdapterManager</code>,
which can take care of figuring out how to adapt the instance.</p>
<p>For this to work, the adapter manager needs to be told how to
adapt the type. This can be done declaratively through the <code>plugin.xml</code>
file using the <code>org.eclipse.core.runtime.adapters</code> extension point:</p>
<pre>&lt;plugin&gt;
&lt;extension
point="org.eclipse.core.runtime.adapters"&gt;
&lt;factory
adaptableType="org.eclipse.example.Person"
class="org.eclipse.example.adapters.PersonPropertiesSourceAdapterFactory"&gt;
&lt;adapter
type="org.eclipse.ui.views.properties.IPropertySource"&gt;
&lt;/adapter&gt;
&lt;/factory&gt;
&lt;/extension&gt;
&lt;/plugin&gt;</pre>
<p>This extension defines an adapter for instances of the <code>org.eclipse.example.Person</code>
class. When asked to adapt to the <code>org.eclipse.ui.views.properties.IPropertySource</code>,
the <code>PersonPropertiesSourceAdapterFactory</code> is used.
This factory class is defined as such:</p>
<pre>public class PersonPropertiesSourceAdapterFactory implements IAdapterFactory {
public Object getAdapter(Object adaptableObject, Class adapterType) {
if (adapterType == IPropertySource.class)
return new PersonPropertySource((Person)adaptableObject);
return null;
}
public Class[] getAdapterList() {
return new Class[] {IPropertySource.class};
}
}</pre>
<p>The <code>getAdapter</code> method does the heavy lifting and
creates the adapter. The <code>getAdapterList</code> returns a list of
the types of adapters the factory can create.</p>
<p>Adapter factories can also be registered programmatically using
APIs on the <code>AdapterManager</code> class.</p>
<p>With the adapters registered, we can make one more
change to the code. When adapters are used, they are usually used in
three steps:</p>
<ol>
<li>If the object implements the required interface, use the
object</li>
<li>If the object implements <code>IAdaptable</code>, call the <code>getAdapter</code>
method and use the returned object; if something other than <code>null</code>
is answered, use the returned value</li>
<li>Get the <code>AdapterManager</code> to try and adapt the
object</li>
</ol>
<p>The final step actually makes the current implementation of <code>getAdapter</code>
in the <code>Person</code> class redundant. We can simply remove it, and
remove the reference to the <code>IAdaptable</code> interface from the
class. That is, the <code>Person</code> class simplifies to:</p>
<pre>public class Person {
private String name;
private Object street;
private Object city;
public Person(String name) {
this.name = name;
this.street = "";
this.city = "";
}
...
}</pre>
<p>Voila! the domain class is totally decoupled from the adapter
type. Instances of the person class, when selected in your favourite
view will populate the Properties view.</p>
<h2>Adapting</h2>
<p>The adapter framework is great for getting your objects to tightly integrate with existing parts
of the Eclipse infrastructure, while remaining loosely coupled with the
actual implementation. Loose coupling with tight integration is pretty
powerful stuff.</p>
<p>The <em>"Image Preview" view</em>, is example plug-in that displays the image
(if one is available) for a file selected in the workbench. The best part is that the view is completely
decoupled from the Resources API. That is, it doesn't know anything
about files, directories, workspaces, or anything else in particular. The
view is shown in action below:</p>
<div class="figure"><img src="images/image-viewer.png"/></div>
<p>The code for the Image Preview view can accessed via the Eclipse Evangelism
site:</p>
<pre><a href="http://www.eclipse.org/evangelism/samples/imageviewer/">http://www.eclipse.org/evangelism/samples/imageviewer/</a></p></pre>
<p> By
implementing the image viewer using adapters, it can be easily made to
display an image for different kinds of selected objects by providing a
new adapter. A natural extension of this is that we can use the Image
Preview view in a <a href="http://www.eclipse.org/rcp">Rich Client Platform</a> (RCP)
application to display an image for my domain objects (assuming that
this makes sense, of course).</p>
<p>The Image Preview view listens to the workbench selection
service. When a selection occurs in a view (such as the Package
Explorer, or Navigator), the selection service notifies registered
listeners. When the Image Preview
view is notified of the selection change, it attempts to adapt the
selected object to the <code>ImageProvider</code> interface (which is
defined in the same bundle as the view). The <code>ImageProvider</code> is then used
to obtain an image.</p>
<p>Recall the three steps to find an adapter:</p>
<ol>
<li>If the selected object implements the <code>ImageProvider</code> interface, use the
object</li>
<li>If the object implements <code>IAdaptable</code>, call the <code>getAdapter</code>
method asking for an <code>ImageProvider</code>; if something other than <code>null</code>
is answered, use the returned value</li>
<li>Get the <code>AdapterManager</code> to try and adapt the
object</li>
</ol>
<p>The <code>getImageProvider</code> method in the Image Preview view looks like this:</p>
<pre>private ImageProvider getImageProvider(Object object) {
// First, if the object is an ImageProvider, use it.
if (ImageProvider.class.isInstance(object)) return (ImageProvider)object;
// Second, if the object is adaptable, ask it to get an adapter.
ImageProvider provider = null;
if (object instanceof IAdaptable)
provider = (ImageProvider)((IAdaptable)object).getAdapter(ImageProvider.class);
// If we haven't found an adapter yet, try asking the AdapterManager.
if (provider == null)
provider = (ImageProvider)Platform.getAdapterManager().<strong>loadAdapter</strong>(object, ImageProvider.class.getName());
return provider;
}</pre>
<p>The first step, is to see if the selected object already
implements our interface. If it does, we cast and return it. If we make
it to the second step, we ask the object if it is adaptable (i.e. does
it implement the <code>IAdaptable</code> interface). If it does, we use
that method to attempt to find an adapter. If that method fails (returns
<code>null</code>), the <code>AdapterManager</code> is used. Ultimately,
this method may fail to find an appropriate adapter and return <code>null</code>.</p>
<p>The selected object doesn't need to know anything about the Image
Preview view. Conversely, my Image Preview view knows nothing about
files or any other particular type of object. The adapter implementor knows about both (it really only needs to know
about the <code>ImageProvider</code> interface).</p>
<p>Note that there are two different ways to ask the <code>AdapterManager</code>
to adapt an object: <code>getAdapter</code> or <code>loadAdapter</code>
(which is highlighted in the snippet). Both methods will find
programmatically- and declaratively-registered adapters, however the <code>getAdapter</code>
method will only find declaratively-registered adapters <em>if the
bundle that contributes them has already been activated</em>. The <code>loadAdapter</code>
will load and activate the bundle (if required) as part of the process.</p>
<h2>Conclusion</h2>
<p></p>
</div>
</body>
</html>