NEW - bug 225189: Article on Adapters https://bugs.eclipse.org/bugs/show_bug.cgi?id=225189
diff --git a/Article-Adapters/index.html b/Article-Adapters/index.html new file mode 100755 index 0000000..29553c4 --- /dev/null +++ b/Article-Adapters/index.html
@@ -0,0 +1,421 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<html> +<head> +<title>Adapters in Three Parts</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 in Three Parts</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. The adapter framework +allows views to work with object that they don't have prior knowledge of. </p> +<div class="author">By Wayne Beaton, The Eclipse Foundation</div> +<div class="copyright">Copyright © 2008 The Eclipse +Foundation.</div> +<div class="date">June 9, 2008</div> +</div> + +<div class="content"> +<h2>Introduction</h2> + +<p>The <a href="http://c2.com/cgi/wiki?AdapterPattern">adapter +pattern</a> is used extensively in Eclipse. The use of this pattern allows +plug-ins to be loosely coupled, yet tightly integrated. As applications +become larger and more complex, reduced coupling becomes more and more +important.</p> + +<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 properies 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.</p> + +<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>If you want to leverage the Properties view in from your own +views, the first step is to make sure that the workbench selection +service knows about the object selected in your view. If you're using a +JFace table or tree, then this step is easy (if you're doing something +else, <a + href="http://www.eclipse.org/articles/Article-WorkbenchSelections/article.html">"Eclipse +Workbench: Using the Selection Service"</a> by Marc R. Hoffmann will +help you determine how to best contribute the selection). All you need +to do is invoke the <code>setSelectionProvider</code> method in your +view's <code>createPartControl</code> method:</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>The important part is marked in bold. 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 implements IPropertySource { + 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, my 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>I indicated earlier that this solution is "not necessarily <em>[the]</em> +most correct". This is because, for this to work, my 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 view and this not a good thing. This where adapters come in.</p> + +<h2>Part One: 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 Property 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 Person class). If the selected object is adaptable, +it is asked—via the <code>getAdapter</code> method—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 the method returns an adapter, it is used by the Property view to +gather properties. Our implementation of adapter fits this bill and so +is used.</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 IPropertySource 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>Part Two: Decoupling</h2> + +<p>The next step is to completely decouple the domain class from <code>IPropertySource</code>. +To do this, I can 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:</p> +<pre><plugin> + <extension + point="org.eclipse.core.runtime.adapters"> + <factory + adaptableType="org.eclipse.example.Person" + class="org.eclipse.example.adapters.PersonPropertiesSourceAdapterFactory"> + <adapter + type="org.eclipse.ui.views.properties.IPropertySource"> + </adapter> + </factory> + </extension> +</plugin></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> should be 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 all safely 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 an 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>Part Three: Adapting</h2> +Over the past week or so, I've been <a + href="http://dev.eclipse.org/blogs/wayne/2007/10/23/adapters-part-deux/">discussing</a> +how you can make your objects adaptable into different forms. This 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>A <a + href="http://wbeaton.blogspot.com/2006/07/loosely-coupled-but-tightly-integrated.html">while +back</a>, I created an <em>"Image Preview" view</em> that displays the image +(if one is available) for a file selected in the workbench (it works +best for image files). 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 along those lines. By +implementing the image viewer using adapters, I can "teach" my view to +display an image for different kinds of selected objects by providing a +new adapter. A natural extension of this is that I can use my Image +Preview view in an <a href="http://www.eclipse.org/rcp">RCP</a> +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 (see <a + href="http://www.eclipse.org/articles/Article-WorkbenchSelections/article.html">here</a> +for more information on the selection service). 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 +part of my implementation). The <code>ImageProvider</code> is then used +to obtain an image. 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. The adapter interface knows about both (it really only knows +about the <code>ImageProvider</code> interface).</p> + +<p>I'm curious about the history of this little pattern (which is +repeated many times). It seems that there is an opportunity here to have +a higher-level API in the <code>AdapterManager</code> that takes all of +these steps, but I assume that there is a good (or at least historical) +reason for it being the way that it is.</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 been activated</em>. The <code>loadAdapter</code> +will load and activate the bundle (if required) as part of the process.</p> + +</div> +</body> +</html>