platform-ui-home/components-proposal/ComponentFrameworkProposal.html
Parent Directory
|
Revision Log
Revision 1.1 -
(download)
(as text)
(annotate)
Tue Nov 2 22:51:37 2004 UTC (5 years ago) by sxenos
Branch: MAIN
Tue Nov 2 22:51:37 2004 UTC (5 years ago) by sxenos
Branch: MAIN
Updated document to include multiplexing services. Renamed to "component framework"
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="content-type">
<title>Component Framework proposal</title>
</head>
<body>
<span style="font-weight: bold;"></span>
<br>
<div style="text-align: center;"><font size="+3"><span
style="font-weight: bold;">Component Framework Proposal</span></font><br>
</div>
<br>
<div style="text-align: center;">Revision 1.0.3<br>
By Stefan Xenos and Nick Edgar<br>
Last modified 2004/11/02<br>
</div>
<br>
<br>
<br>
<font size="+3"><span style="font-weight: bold;">Table of Contents</span></font><br>
<ul id="mozToc">
<!--mozToc h1 1 h2 2 h3 3 h4 4 h5 5 h6 6--><li><a href="#mozTocId511305">1.0
Introduction
</a>
<ul>
<li><a href="#mozTocId29093">1.1 Robustness / leak proofing
</a></li>
<li><a href="#mozTocId223982">1.2 Nesting of components</a></li>
<li><a href="#mozTocId634024">1.3 Creating parts outside the
workbench</a></li>
<li><a href="#mozTocId800379">1.4 Scalability</a></li>
<li><a href="#mozTocId866479">1.5 Ease of use
</a></li>
</ul>
</li>
<li><a href="#mozTocId416167">2.0 Component Framework
</a>
<ul>
<li><a href="#mozTocId489102">2.1 Instantiating a view
</a></li>
<li><a href="#mozTocId717633">2.2 Instantiating components in
general
</a></li>
<li><a href="#mozTocId450231">2.3 Creating dependent components
on demand </a></li>
<li><a href="#mozTocId928619">2.4 Declaring Global Services
</a></li>
<li><a href="#mozTocId749133">2.5 Using services</a></li>
<li><a href="#mozTocId705695">2.6 Lifecycle</a></li>
<li><a href="#mozTocId543223">2.7 Dynamic Services</a></li>
<li><a href="#mozTocId886834">2.8 Optional Services</a></li>
<li><a href="#mozTocId163729">2.9 Multiplexing services</a></li>
</ul>
</li>
<li><a href="#mozTocId707022">3.0 Views and Editors as Components</a></li>
</ul>
<br>
<h1><a class="mozTocH1" name="mozTocId511305"></a>1.0 Introduction<br>
</h1>
This proposal outlines a framework for managing complex executable
extensions. This framework is a first step toward allowing views and
editors to be combined recursively, and created inside arbitrary SWT
composites. Although it is intended for creating reusable UI
components, the framework is useful for any extension point that
creates complicated Java objects. For this reason, we will refer to the
objects being created as "components" even though our components are
typically views and editors.<br>
<br>
This proposal is broken into three parts. The introduction describes
the motivation for creating the component framework and its
requirements. The second section describes the component framework in
itself, which could be used for any executable extension point. The
third section describes how the workbench will use the component
framework for views and editors.<br>
<br>
Primary goals:<br>
<ul>
<li>Robustness / leak proofing</li>
<li>Nesting of components</li>
<li>Allow components to be reused outside the workbench</li>
<li>Scalability (allow an open-ended set of components)</li>
<li>API versioning<br>
</li>
<li>Ease of use</li>
</ul>
<h2><a class="mozTocH2" name="mozTocId29093"></a>1.1 Robustness / leak
proofing<br>
</h2>
Much of the Eclipse API is currently accessed through singleton
objects. This makes it easy for a view or editor to leak listeners,
fail to clean up reference counts, leak OS resources, etc. since there
is no way of tracking which resources were allocated by a particular
view. This problem would be reduced if views and editors were more like
mini-applications. The view or editor would access the rest of the
world through a set of local services. When the view or editor is
destroyed so would all of the services allocated for it. This gives
each service a chance to clean up after itself. <br>
<br>
For example, instead of reaching into a global
preference store, a view or editor could access all preferences through
a local preference
service. A unique instance of the local preference service would be
created for each view, and would be disposed with the view. When the
preference service is destroyed it would clear its listener list,
ensuring that the view will not leak any global listeners. This example
shows what we mean by a component. Essentially, a
component is an object that communicates with the rest of the
application through a set of interfaces given to it in its constructor.<br>
<span style="font-weight: bold;"></span>
<h2><a class="mozTocH2" name="mozTocId223982"></a>1.2 Nesting of
components</h2>
There is demand for the ability to embed views and editors inside one
another. Some examples:<br>
<ul>
<li>An XML editor might embed the properties view</li>
<li>A refactoring wizard might include a source editor</li>
<li>A plugin may wish to create a set of pluggable UI components that
are not views or editors themselves, but can be used inside any view or
editor</li>
<li>Various workbench objects (like the PartSashContainer that
handles the layout of docked parts within the workbench) could be
exposed as API<br>
</li>
</ul>
Many downstream plugins have solved these problems by creating their
own frameworks for reusable UI components. Unfortunately, only works
for specific views and editors, does not encourage interoperability
between plugins that have adopted different frameworks, and requires a
lot of work. The goal here is to adopt a framework in the workbench
itself that allows all editors, views, and other workbench objects to
be easily nested.<br>
<h2><a class="mozTocH2" name="mozTocId634024"></a>1.3 Creating parts
outside the workbench</h2>
It should be possible to instantiate views and editors outside the
workbench. Some examples:<br>
<ul>
<li>Unit-test editors and views by instantiating them within a JUnit
test suite.</li>
<li>Create an RCP application that does not depend on the workbench
but includes view-like pluggable parts
that could also be used as views within Eclipse</li>
</ul>
<h2><a class="mozTocH2" name="mozTocId800379"></a>1.4 Scalability</h2>
The workbench currently offers a closed set of services to parts. This
forces parts to reach to global objects whenever they need something
that isn't available from their site. It should be possible for any
plugin to contribute to the set of local API available to a part, and
it
should be possible to instantiate a part even if its parent doesn't
know about all of a part's dependencies.<br>
<h2><a class="mozTocH2" name="mozTocId866479"></a>1.5 Ease of use<br>
</h2>
Views and editors currently have a complicated lifecycle that must be
managed by the workbench. This complexity should not be exposed to
client code. <br>
<ul>
<li>It should only require one method call to create a component and
one method call to destroy it.</li>
<li>There should not be unnecessary duplication between XML and java
code.</li>
<li>Components should not
be responsible for dealing with error conditions (such as missing
dependencies) that can be detected by the framework.</li>
<li>Components should not need to implement interfaces they don't
care about.</li>
<li>A parent context should not need to provide child components with
interfaces it doesn't care about.<span style="font-weight: bold;"></span></li>
</ul>
<h1><a class="mozTocH1" name="mozTocId416167"></a>2.0 Component
Framework<br>
</h1>
Components are pluggable objects that are build using constructor
injection. Constructor injection means that the framework looks at the
object's constructor to determine what it needs to do to build that
object.<br>
<br>
Components:<br>
<ul>
<li>Have exactly one constructor</li>
<li>Take zero or more other components as arguments to their
constructor</li>
<li>Are fully initialized by their constructor</li>
</ul>
Components do not:<br>
<ul>
<li>Take arrays or primitives as arguments to their constructor</li>
<li>Take more than one argument of the same type in their constructor<br>
</li>
<li>Need to support any particular base class or interface</li>
</ul>
Any class with these properties can be used as a component. Since
components are plain-old-java-objects, they do not need to depend on
the component framework. Components are fully initialized by
their constructor, meaning it is never necessary
to call an initialize method or any combination of set methods after
constructing the object. Components never accept null as an argument
to their constructor.<br>
<br>
For example, if a view could be created as a component it might look
like this:<br>
<br>
<div style="margin-left: 40px;"><code>/**</code><br>
<code> * "Hello world" view using the service framework</code><br>
<code> */</code><br>
<code>public class HelloWorldView {</code><br>
<code> public HelloWorldView(Composite parent) {</code><br>
<code> Label helloWorld = new
Label(parent, SWT.NONE);</code><br>
<code>
helloWorld.setText("Hello
world");</code><br>
<code> }</code><br>
<code>}</code><br>
<code></code></div>
<code><br>
</code>The HelloWorldView component depends on one service: a Composite
created for it by whoever instantiated the view. Compare this with the
same view written using the existing API:<br>
<br>
<div style="margin-left: 40px;"><code>/**</code><br>
<code> * "hello world" view using the Eclipse 3.0 API.</code><br>
<code> */</code><br>
<code>public class HelloWorldView extends ViewPart {</code><br>
<code> public void createPartControl(Composite
parent) {</code><br>
<code></code><code> Label
helloWorld = new Label(parent, SWT.NONE);</code><br>
<code>
helloWorld.setText("Hello
world");</code><code> </code><br>
<code> }</code><br>
<code></code><br>
<code> public void setFocus() {</code><br>
<code> }</code><br>
<code>}</code><br>
</div>
<br>
The main difference is that the component version does not require the
ViewPart base class and is fully initialized after construction. In
both cases, the extension point markup would look like this:<br>
<br>
<div style="margin-left: 40px;"><code><extension
point="org.eclipse.ui.views"></code><br>
<code> <view</code><br>
<code> name="Title Test View"</code><br>
<code> icon="icons\view.gif"</code><br>
<code>
class="org.eclipse.ui.mytest.HelloWorldView"</code><br>
<code> id="</code><code>org.eclipse.ui.mytest.HelloWorldView</code><code>ID"></code><br>
<code> </view></code><br>
<code></extension></code><br>
</div>
<br>
<h2><a class="mozTocH2" name="mozTocId489102"></a>2.1 Instantiating a
view<br>
</h2>
Components are instantiated using factories. For the moment, let's
focus on views before we explore components in general. Views could be
created using a method on IWorkbenchPage that would look something like
this. Note that this example is only intended to illustrate the general
idea -- the actual protocol for creating views is likely to change.<br>
<br>
<div style="margin-left: 40px;"><code>/**</code><br>
<code> * Creates a view of the given type inside the given
composite.
The caller must dispose the container once they are done with it.</code><br>
<code> *</code><br>
<code> * @param viewId id of the view extension to use</code><br>
<code> * @param parentComposite parent composite for the view</code><br>
<code> * @return an IContainer that contains all components needed
for the view</code><br>
<code> */</code><br>
<code>IContainer createView(String viewId, Composite parentComposite);</code><br>
<code></code></div>
<code></code><br>
<br>
This creates a new instance of the view in the given composite. Notice
that the factory method doesn't return an instance of the view itself
but an instance of IContainer, which looks something like this:<br>
<br>
<div style="margin-left: 40px;"><code>/**</code><br>
<code> * Main interface to a component.</code><br>
<code> */</code><br>
<code>public interface IContainer extends IAdaptable {</code><br>
<code></code> public void dispose();<br>
}<br>
</div>
<br>
This interface wraps the component and all of its dependencies. Once
we've obtained an IContainer handle, we are obligated to dispose it
when we are done with it. All access to the component is done through
adapters, which insulates
the application from changes in individual views. For example, if a
view that previously implemented the IViewInterface1 migrates to using
the newer IViewInterface2, its container will continue to work as long
as someone has provided an adapter between the two versions of
IViewInterface. The getAdapter() method on IComponent searches for
adapters in the following order:<br>
<br>
1. If the component itself implements the adapter type, it returns the
component.<br>
2. If the component itself implements IAdapter, and the component's
getAdapter(...) method returns non-null, we return that adapter.<br>
3. If the adapter manager has an adapter between the component and the
adapter type, we return that adapter.<br>
4. If the container's factory can construct a component of the
requested type, we
create that component, add it as a local dependency to the IContainer,
and
return it.<br>
5. Return null<br>
<br>
For example, it would be possible to create our HelloWorldView (above)
in a modal dialog like this:<br>
<br>
<div style="margin-left: 40px;"><code>void
createHelloWorldViewInADialog(IWorkbenchPage page) {</code><br>
<code> Display display = Display.getDefault()</code><br>
<code> Shell shell = new Shell(Display.getDefault());</code><br>
<code></code><br>
<code> shell.setLayout(new FillLayout());</code><br>
<code></code><br>
<code> // Create the view and its widgets</code><br>
<code> IContainer myView =
workbenchPage.createView("org.eclipse.ui.mytest.HelloWorldViewID",
shell);</code><br>
<code></code><br>
<code> shell.open();</code><br>
<code></code><br>
<code> while (!shell.isDisposed()) {</code><br>
<code> if (!display.readAndDispatch ())
display.sleep ();</code><br>
<code> }</code><br>
<code></code><br>
<code> // Dispose the view</code><br>
<code> myView.dispose();</code><br>
<code>}</code><br>
<code></code></div>
<h2><a class="mozTocH2" name="mozTocId717633"></a>2.2 Instantiating
components in general<br>
</h2>
All components are constructed using an IContainerFactory, however most
component-based extension points will provide some
sort of convenience method to wrap their IContainerFactory. The
IWorkbenchPage.createView method in the
previous section is a convenience method for the <span
style="font-style: italic;">org.eclipse.ui.views </span>extension
point.<br style="font-style: italic;">
<br>
IContainerFactory looks like this:<br>
<br>
<div style="margin-left: 40px;"><code>/**<br>
* Factory for IContainer instances. The default factory is
returned by<br>
* Components.getFactory(). Clients wishing to implement their own
specialized <br>
* factories should call createDerivedFactory() to get access to a
factory<br>
* whose behavior can be modified programmatically. Not intended
to be <br>
* implemented by clients. <br>
* <br>
* @since 3.1<br>
*/<br>
public interface IContainerFactory {<br>
<br>
/**<br>
* Creates and returns a new IContainer
instance, given the <br>
* implementation class for its component. The
caller MUST call IContainer.dispose() <br>
* when it is done with the component. The
factory does not need any prior<br>
* knowledge of the component class be<br>
* <br>
* @param componentImplementation concrete
class to be instantiated by the factory. The class<br>
*
must be a valid component (it must have exactly one constructor which
only<br>
*
references other component interfaces known to this factory).<br>
* @return a newly constructed
<code>IContainer</code> instance<br>
* @throws MissingDependencyException if the
requested component depends on another component that<br>
* cannot be constructed
by this factory<br>
* @throws CoreException if there is a more
permanent problem with using the given class as <br>
* a component <br>
* @since 3.1<br>
*/<br>
public IContainer createContainer(Class
componentImplementation) throws MissingDependencyException,
CoreException;<br>
<br>
/**<br>
* Creates a specialization of this factory. By
default, the specialized<br>
* factory will have the same behavior as its
parent. However, the derived<br>
* factory can add, or override the
implementation for any components.<br>
* <br>
* @return new factory instance that allows
individual components to be overridden.<br>
* By default, the derived factory will
delegate all of its behavior to the receiver.<br>
* Changes in the receiver will affect the
derived factory.<br>
* @since 3.1<br>
*/<br>
public IMutableContainerFactory
createDerivedFactory();<br>
}</code><code></code><br>
<code></code></div>
<code></code><br>
Notice that createComponent throws two types of exception. A
CoreException indicates a problem with the component itself, and a
MissingDependencyException indicates that some other component or
adapter could not be found. This can help locate the cause of problems,
and a robust extension point may want to disable components that throw
CoreExceptions.<br>
<br>
All arguments to a component's constructor are provided by its factory.
Different
factories know how to create different types of components or will
provide
different implementations for the same components. All factories are
derived from the root factory, which is returned by
Components.getFactory(). A plugin can create derived factories to
supply services to one specific extension point. It is also possible to
create a derived factory in order to pass information to the
constructor of one specific object, as we will do in this example. In
our case, we will create a container factory to pass a specific
Composite into the constructor of HelloWorldView. The code looks like
this:<br>
<code></code><code><br>
</code>
<div style="margin-left: 40px;"><code>// Get the global factory</code><br>
<code>IContainerFactory viewFactory =
Components.getFactory();</code><br>
<code></code><br>
<code>// Create the Composite for the view</code><br>
<code></code><code>Composite viewComposite = new
Composite(parentComposite, SWT.NONE);</code><br>
<code>viewComposite.setLayout(new FillLayout();</code><br>
<code></code><br>
<code>// Create a specialized factory that knows about the view's
composite and plugin bundle</code><br>
<code>IMutableContainerFactory derivedFactory =
viewFactory.createDerivedFactory();</code><br>
<code></code><br>
<code>// Add the view's composite as a service instance</code><br>
<code>derivedFactory.addComponentInstance(viewComposite);</code><br>
<code></code><br>
<code>// Add the view's plugin bundle as a service instance (not
required in this example, but this</code><br>
<code>// is recommended practise for any component created from an
extension point).</code><br>
<code>derivedFactory.addComponentInstance(pluginBundle);</code><br>
<code></code><br>
<code>// Create the view. Provide its constructor and some
context (the page that created it).</code><br>
<code>IContainer view =
derivedFactory.createContainer(HelloWorldView.class);</code><br>
<code></code><br>
<code>// Do something with the view</code><br>
<code>// ...</code><br>
<code></code><br>
<code>// Now clean up</code><br>
<code>view.dispose();</code><br>
<code>viewComposite.dispose();</code><br>
<code></code></div>
<code><br>
</code><br>
This code will work, but it has two problems:<br>
<br>
1. Even if HelloWorldView didn't require a Composite, we would still
have created one (which is wasteful).<br>
2. We need to manually dispose the view's composite after we're done
with it. This defeats the point of IComponent.dispose(), which is
supposed to clean up all of the component's dependencies automatically.<br>
<h2><a class="mozTocH2" name="mozTocId450231"></a>2.3 Creating
dependent components on demand </h2>
Rather than managing the view's Composite ourselves, we could supply a
factory that knows how to create and destroy Composites as needed. Such
a factory would look like this:<br>
<br>
<div style="margin-left: 40px;"><code>/**</code><br>
<code> * ServiceAdapter for creating and managing SWT Composites
as services.</code><br>
<code> */</code><br>
<code>public class CompositeFactory extends ComponentAdapter {</code><br>
<code></code><br>
<code> private Composite parent;</code><br>
<code> </code><br>
<code> /**</code><br>
<code> * The SWT composites created by this
factory will all be children of the given</code><br>
<code> * composite.</code><br>
<code> */</code><br>
<code> public CompositeFactory(Composite parent) {</code><br>
<code> this.parent = parent;</code><br>
<code> }</code><br>
<code> </code><br>
<code> protected Object create(IAdaptable
availableServices) throws CoreException {</code><br>
<code> // Create a new Composite
which will be used as a service.</code><br>
<code> // If we needed any
additional</code><br>
<code> Composite newChild =
new Composite(parent, SWT.NONE);</code><br>
<code> newChild.setLayout(new
FillLayout());</code><br>
<code> return newChild;</code><br>
<code> }</code><br>
<code></code><br>
<code> protected void dispose(Object service) {</code><br>
<code>
((Composite)service).dispose();</code><br>
<code> }</code><br>
<code></code><br>
<code> public String getInterfaceName() {</code><br>
<code> // Return the fully
qualified class name of the Composite class. The framework will call</code><br>
<code> // this method to determine
what type of service will be constructed by this factory.</code><br>
<code> return
Composite.class.getName();</code><br>
<code> }</code><br>
<code>}</code><br>
<code></code></div>
<br>
Using CompositeFactory, we could construct HelloWorldView like this:<br>
<br>
<div style="margin-left: 40px;"><code>// Get the global factory</code><br>
<code>IContainerFactory viewFactory =
Components.getFactory();</code><code></code><br>
<code></code><br>
<code>// Create a specialized factory that knows about the view's
composite and plugin bundle</code><br>
<code>IMutableContainerFactory derivedFactory =
viewFactory.createDerivedFactory();</code><br>
<code></code><br>
<code>// Add the view's composite as a service instance</code><br>
<code>derivedFactory.addComponentFactory(new
CompositeFactory(parentComposite));</code><br>
<code></code><br>
<code>// Add the view's plugin bundle as a service instance (not
required in this example, but this</code><br>
<code>// is recommended practise for any component created from an
extension point).</code><br>
<code>derivedFactory.addComponentInstance(pluginBundle);</code><br>
<code></code><br>
<code>// Create the view. Provide its constructor and some
context (the page that created it).</code><br>
<code>IContainer view =
derivedFactory.createComponent(HelloWorldView.class);</code><br>
<code></code><br>
<code>// Do something with the view</code><br>
<code>// ...</code><br>
<code></code><br>
<code>// Now clean up</code><br>
<code>view.dispose();</code><br>
<code></code></div>
<code>
</code><br>
The composite will now be allocated as needed and disposed inside the
call to view.dispose().<br>
<br>
<code></code>
<h2><a class="mozTocH2" name="mozTocId928619"></a>2.4 Declaring Global
Services<br>
</h2>
Services are a type of component that can be used by other components.
Services are registered through the <span style="font-style: italic;">org.eclipse.ui.component.service
</span>extension
point. Services are global in the sense that they are created by
factories
that are visible everywhere, however each instance of a services is
associated with a specific IContainer. Each service implements a
service interface. Other components can use the service interface in
their constructor. When they do so, a new instance of the service will
be created for that component which will be managed by the same
IContainer. <br>
<br>
Each component supplies an implementation and an interface. Here is an
example service interface:<br>
<code><br>
</code>
<div style="margin-left: 40px;"><code>/**</code><br>
<code> * Service interface: </code><br>
<code> *</code><br>
<code> * Provides a context for reporting and logging exceptions.</code><br>
<code> */</code><br>
<code>public interface IErrorContext {</code><br>
<code> /**</code><br>
<code> * Create an IStatus error describing the
given
throwable</code><br>
<code> */</code><br>
<code> public IStatus createStatus(Throwable t);</code><br>
<code></code><br>
<code> /**</code><br>
<code> * Create an IStatus message with the
given
severity, message, and (optional) throwable</code><br>
<code> */</code><br>
<code> public IStatus createStatus(int severity,
String
message, Throwable t);</code><br>
<code></code><br>
<code> /**</code><br>
<code> * Logs an IStatus message</code><br>
<code> */</code><br>
<code> public void log(IStatus status);</code><br>
<code></code><br>
<code> /**</code><br>
<code> * Logs an exception to the system log</code><br>
<code> */</code><br>
<code> public void log(Throwable t);</code><br>
<code>}</code><br>
<code></code></div>
<code><br>
<br>
</code>Here is the associated service implementation:<br>
<code><br>
</code>
<div style="margin-left: 40px;"><code>/**</code><br>
<code> * Service implementation:</code><br>
<code> *</code><br>
<code> * Creates and logs errors in the context of a plugin bundle</code><br>
<code> */</code><br>
<code>public class ErrorContext implements IErrorContext {</code><br>
<code> private Bundle pluginBundle;</code><br>
<code></code><br>
<code> public ExceptionLoggingService(Bundle
pluginBundle) {</code><br>
<code> this.pluginBundle =
pluginBundle;</code><br>
<code> }</code><br>
<code></code><br>
<code> public IStatus createStatus(Throwable t) {</code><br>
<code> String message = t.getMessage();</code><br>
<code> if (message == null) {</code><br>
<code> message =
t.getString();</code><br>
<code> }</code><br>
<code> return createStatus(Status.ERROR,
message, t);</code><br>
<code> }</code><br>
<code></code><br>
<code> public IStatus createStatus(int severity,
String
message, Throwable t) {</code><br>
<code> return new Status(severity,
pluginBundle.getSymbolicName(), Status.OK, message, t);</code><br>
<code> }</code><br>
<code></code><br>
<code> public void log(Throwable t) {</code><br>
<code> log(createStatus(t));</code><br>
<code> }</code><br>
<code></code><br>
<code> public void log(IStatus status) {</code><br>
<code> Plugin plugin =
Platform.getPlugin(pluginBundle.getSymbolicName());</code><br>
<code> plugin.getLog().log(status);</code><br>
<code> }</code><br>
<code>}</code><br>
<code></code></div>
<code></code><br>
Finally, here is the extension markup:<br>
<br>
<div style="margin-left: 40px;"><code><extension
point="org.eclipse.ui.component.service"></code><br>
<code> <service
</code><br>
<code>
class="org.eclipse.ui.workbench.ErrorContext"</code><br>
<code> </code><code>interface="</code><code>org.eclipse.ui.workbench.IErrorContext</code><code>"</code><code>></code><br>
<code> </service></code><br>
<code></extension></code><br>
</div>
<br>
Notice that the extension XML needs to indicate which service interface
is being implemented by the service, but not its dependencies. When it
comes time to create the service, the framework detects that the
ErrorContext needs to be associated with a plugin Bundle by examining
its constructor. This means that if plugin A defines the ErrorContext
service and plugin B defines a view that uses the service, then all of
the status messages constructed by the view will be associated with the
view's own plugin - not the plugin that defined the ErrorContext
service.<br>
<h2><a class="mozTocH2" name="mozTocId749133"></a>2.5 Using services</h2>
This example shows how a component can use a service.<br>
<br>
<div style="margin-left: 40px;"><code>public class MyView {</code><br>
<code> private IErrorContext errorContext;</code><br>
<code></code><br>
<code> public MyView(</code><code>Composite parent, </code><code>IErrorContext
errorContext) {</code><br>
<code> this.errorContext = errorContext;</code><br>
<code></code><br>
<code></code><code> Button errorButton =
new
Button(parent, SWT.PUSH);</code><br>
<code> errorButton.setText("Log an
exception");</code><br>
<code> </code><br>
<code>
errorButton.addSelectionListener(new
SelectionAdapter() {</code><br>
<code> public void
widgetSelected(SelectionEvent e) {</code><br>
<code>
try {</code><br>
<code>
// Throw a NPE</code><br>
<code>
String myString = null;</code><br>
<code>
String bogusCode = myString.substring(10, 30);</code><br>
<code>
} catch (Exception e) {</code><br>
<code>
// Log the NPE using the error context service</code><br>
<code>
errorContext.log(e);</code><br>
<code>
}</code><br>
<code> }</code><br>
<code> });</code><br>
<code></code><code> }</code><br>
<code></code><code>}</code><br>
<code></code></div>
<code><br>
</code>This view creates a button which, when pressed, will throw a NPE
and log it. The status message will be constructed and logged using the
IErrorContext service we defined above.<br>
<br>
<h2><a class="mozTocH2" name="mozTocId705695"></a>2.6 Lifecycle</h2>
Many components need to do explicit cleanup. Components
that need to perform cleanup will implement the
IDisposable interface. For example, the following component attaches a
listener to a global preference store and detaches it when done.<br>
<br>
<div style="margin-left: 40px;"><code>/**</code><br>
<code> * Provides access to a plugin's preference store in a
manner that
prevents listener leaks.</code><br>
<code> */</code><br>
<code>public interface IPreferences {</code><br>
<code> public String getString(String prefId);</code><br>
<code> public void setString(String prefId, String
value);</code><br>
<code> public void
addListener(IPropertyChangeListener l);</code><br>
<code> public void
removeListener(IPropertyChangeListener
l);</code><br>
<code>}</code><br>
<code></code><br>
<code>/**</code><br>
<code> * Concrete implementation of the IPreferences interface</code><br>
<code> */</code><br>
<code>public LocalPreferenceStore implements IPreferences, IDisposable {</code><br>
<code> ListenerList listeners = new ListenerList();</code><br>
<code> Preferences prefs;</code><br>
<code> </code><br>
<code> private class PropertyChangeListener
implements
Preferences.IPropertyChangeListener {</code><br>
<code> /*</code><br>
<code> * @see
org.eclipse.core.runtime.Preferences.IPropertyChangeListener#propertyChange(org.eclipse.core.runtime.Preferences.PropertyChangeEvent)</code><br>
<code> */</code><br>
<code> public void
propertyChange(Preferences.PropertyChangeEvent event) {</code><br>
<code>
firePropertyChangeEvent(event.getProperty(), event.getOldValue(),
event.getNewValue());</code><br>
<code> }</code><br>
<code> }</code><br>
<code></code><br>
<code> PropertyChangeListener listener = new
PropertyChangeListener();</code><br>
<code></code><br>
<code> /**</code><br>
<code> * Create a wrapper around the given
bundle's
preference store.</code><br>
<code> */</code><br>
<code> public LocalPreferenceStore(Bundle
pluginBundle) {</code><br>
<code> Plugin plugin =
Platform.getPlugin(pluginBundle.getSymbolicName());</code><br>
<code> prefs =
plugin.getPluginPreferences();</code><br>
<code> prefs.addListener(listener);</code><br>
<code> }</code><br>
<code></code><br>
<code> private void firePropertyChange(String name,
Object
oldValue, Object newValue) {</code><br>
<code> PropertyChangeEvent event =
new
PropertyChangeEvent(this, name, oldValue, newValue);</code><br>
<code> Object[] listeners =
this.listeners.getListeners();</code><br>
<code> for (int i= 0; i <
listeners.length; i++)</code><br>
<code>
((IPropertyChangeListener) listeners[i]).propertyChange(event);</code><br>
<code> }</code><br>
<code></code><br>
<code> /**</code><br>
<code> * This method will automatically be
called
when the component that uses this service is disposed.</code><br>
<code> * It should clean up anything that has
been
allocated by the service.</code><br>
<code> */</code><br>
<code> public void dispose() {</code><br>
<code> prefs.removeListener(listener);</code><br>
<code> }</code><br>
<code></code><br>
<code> public String getString(String prefId) {</code><br>
<code> return prefs.getString(prefId);</code><br>
<code> }</code><br>
<code></code><br>
<code> public void setString(String prefId, String
value) {</code><br>
<code> prefs.setValue(prefId, value);</code><br>
<code> }</code><br>
<code>}</code><br>
<code></code></div>
<code></code><br>
Similarly, components may implement the IDisposable interface. A
component that implements IDisposable will be disposed before any of
its services, as shown by the following example:<br>
<br>
<div style="margin-left: 40px;"><code>public LifecycleService
implements IDisposable {</code><br>
<code> public LifecycleService() {</code><br>
<code>
System.out.println("LifecycleService
created");</code><br>
<code> }</code><br>
<code></code><br>
<code> public void dispose() {</code><br>
<code>
System.out.println("LifecycleService
destroyed");</code><br>
<code> }</code><br>
<code>}</code><br>
<code></code><br>
<code>public LifecycleView implements IDisposable {</code><br>
<code> public LifecycleView(LifecycleService service)
{</code><br>
<code> System.out.println("LifecycleView
created");</code><br>
<code> }</code><br>
<code></code><br>
<code> public void dispose() {</code><br>
<code> System.out.println("LifecycleView
destroyed");</code><br>
<code> }</code><br>
<code>}</code><br>
<code></code><br>
</div>
Finally, we could execute the following code to trace when the service
is created and destroyed:<br>
<div style="margin-left: 40px;"><br>
IMutableContainerFactory factory =
Components.getFactory().createDerivedFactory();<br>
factory.addComponentImplementation(LifecycleService.class);<br>
<br>
System.out.println("Creating a LifecycleView");<br>
<br>
IContainer viewComponent = factory.createComponent(LifecycleView.class);<br>
viewComponent.dispose();<br>
<br>
System.out.println("Creating two LifecycleViews");<br>
<br>
IContainer viewComponent2 =
factory.createComponent(LifecycleView.class);<br>
IContainer viewComponent3 =
factory.createComponent(LifecycleView.class);<br>
viewComponent3.dispose();<br>
viewComponent2.dispose();<br>
<br>
</div>
The code will generate the following output:<br>
<br>
<div style="margin-left: 40px;">Creating a LifecycleView<br>
LifecycleService created<br>
LifecycleView created<br>
LifecycleView destroyed<br>
LifecycleService destroyed<br>
<br>
Creating two LifecycleViews<br>
LifecycleService created<br>
LifecycleView created<br>
LifecycleService created<br>
LifecycleView created<br>
LifecycleView destroyed<br>
LifecycleService destroyed<br>
LifecycleView destroyed<br>
LifecycleService destroyed<br>
<br>
</div>
<h2><a class="mozTocH2" name="mozTocId543223"></a>2.7 Dynamic Services</h2>
Global service factories are dynamic in the sense that they come and go
as plugins are installed or uninstalled. If a plugin providing a
service is uninstalled, a new implementation is selected from a
different plugin. If no other plugin provides that service, the service
becomes unavailable and it will not be possible to construct components
that use it.<br>
<br>
Service instances themselves are not dynamic. Once a service is
created, it will exist for as long as the component it was created for.
Even if the a plugin stops providing a service, all instances of that
service will continue to exist until disposed. All services used by a
component will be created before that component, so components do not
need to query for the existence of services or to wait for services
that will be created after-the-fact. <br>
<br>
Plugins that need more advanced dynamic support should use OSGI
services. The component framework is not intended as a replacement for
OSGI, and there is no point in reimplmenting things that OSGI already
does well.<br>
<h2><a class="mozTocH2" name="mozTocId886834"></a>2.8 Optional Services<br>
</h2>
Sometimes a component does not require a service but can make use of it
service if it exists. This should usually be handled by explicitly
creating an empty global implementation of the service, but sometimes
it is more efficient to have the component explicitly check if the
service exists. This can be done by taking an argument of type
IAdaptable, like this:<br>
<br>
<div style="margin-left: 40px;"><code>/**<br>
* A component with no dependencies, but which can optionally
accept an IMemento for initialization.<br>
*/<br>
public class MyComponent {<br>
<br>
String myName = "default name";<br>
<br>
public MyComponent(IAdaptable optionalServices) {<br>
<br>
</code><code> // Check if an IMemento
service exists.</code><br>
<code> IMemento memento =
(IMemento)optionalServices.getAdapter(IMemento.class);<br>
<br>
if (memento != null) {<br>
String name =
memento.getString("name");<br>
if (name != null) {<br>
myName = name;<br>
}<br>
}<br>
<br>
System.out.println("created component
with name = " + myName);<br>
}<br>
}<br>
<br>
</code></div>
We could create the component like this:<br>
<br>
<div style="margin-left: 40px;"><code>// Create a memento with the name
"custom name"<br>
IMemento memento = XMLMemento.createWriteRoot("test");<br>
memento.putString("name", "custom name");<br>
<br>
// Create a factory which knows about our memento<br>
IMutableContainerFactory context =
Components.getFactory().createDerivedFactory();<br>
context.addComponentInstance(memento);<br>
<br>
// Use the factory to instantiate MyComponent<br>
IContainer component = context.createContainer(MyComponent.class);<br>
// Will print the message "created component with name = custom name"<br>
component.dispose();</code><br>
<br>
</div>
We can also create the component without the optional IMemento:<br>
<br>
<div style="margin-left: 40px;"><code>IContainer component =
Components.getFactory().createContainer(MyComponent.class);<br>
// Will print the message "create component with name = default name"<br>
component.dispose();</code><code></code><br>
</div>
<br>
WARNING: This pattern should be used with care since it adds special
cases to the component code. It is mainly inteded for situations where
a component requires an open-ended set of services or where it is not
possible to offer a default implementation of a service.<br>
<br>
<h2><a class="mozTocH2" name="mozTocId163729"></a>2.9 Multiplexing
services</h2>
In UI code, it is common to create aggregate components that have an
"active" child. Many of the adapters that the aggregate offers its
parent will redirect their implementation to the active child. We refer
to this as "multiplexing" the service. The aggregate itself will know
how to select its active child, but it may not know about all the
services that need to be multiplexed. The default implementation of a
service determines how and if it should be multiplexed.<br>
<br>
This can be explained better using a concrete example. Workbench parts
offer an IFocusable adapter to their parent that allows their parent to
give them focus. <br>
<br>
<code></code>
<div style="margin-left: 40px;"><code>/**<br>
* Parts can implement this interface if they wish to overload the
default<br>
* setFocus behavior.<br>
*/<br>
public interface IFocusable {<br>
/**<br>
* Gives focus to the part<br>
*/<br>
public void setFocus();<br>
}</code><br>
<br>
</div>
This interface should be multiplexed by default. For example, if we
call setFocus() on a multi-page editor that doesn't explicitly
implement the IFocusable interface, it should redirect focus to its
active child. If the part doesn't have an active child, focus should go
directly to the part's main control. To supply this default behavior,
we provide an implementation of IFocusable as a global service:<br>
<br>
<code></code>
<div style="margin-left: 40px;"><code>/**<br>
* Default implementation of the IFocusable service. If a part
doesn't explicitly<br>
* provide an adapter for IFocusable, this implementation will be
used.<br>
*/<br>
public class DefaultFocusable implements IFocusable {<br>
<br>
private Composite control;<br>
private IMultiplexer activePart;<br>
<br>
/**<br>
* Creates the default implementation of
IFocusable, given the main control<br>
* of the part and an IMultiplexer that can be
queried for the active child.<br>
* <br>
* @param toGiveFocus main control of the pane<br>
* @param activePartProvider multiplexer that
returns the active part<br>
*/<br>
public DefaultFocusable(Composite toGiveFocus,
IMultiplexer activePartProvider) {<br>
control = toGiveFocus;<br>
activePart = activePartProvider;<br>
}<br>
<br>
/**<br>
* First, try to give focus to the active
child. If there is no active child, give focus to<br>
* the main control.<br>
*/<br>
public void setFocus() {<br>
// If this part has the notion of
an active child, give that child focus<br>
Object current =
activePart.getCurrent();<br>
if (current != null) {<br>
IFocusable
focusable = (IFocusable)Components.getAdapter(current,
IFocusable.class);<br>
if (focusable
!= null) {<br>
focusable.setFocus();<br>
return;<br>
}<br>
}<br>
<br>
// If the part has no children,
then give focus to the part itself.
<br>
control.setFocus();<br>
}<br>
}</code><br>
<br>
</div>
Here is the XML markup to register the service:<br>
<br>
<div style="margin-left: 40px;"><code><service<br>
interface="org.eclipse.ui.workbench.services.IFocusable"<br>
class="org.eclipse.ui.internal.part.serviceimplementation.DefaultFocusable"/></code><code></code><br>
</div>
<br>
All of the services we have looked at so far have been offered by a
parent for use in a child's constructor. In this case, the service is
offered as an adapter on the child to be used by the parent. In both
cases, the service is declared in the same way. Any service that
supports multiplexing takes an IMultiplexer in its constructor. If the
part does not support multiplexing, the IMultiplexer will always
returns null from getCurrent. Here is an example part that supports
multiplexing:<br>
<code><br>
</code>
<div style="margin-left: 40px;"><code>/**<br>
* Part that explicitly implements IFocusable.<br>
*/<br>
public class Page implements IFocusable {<br>
Text textField;<br>
<br>
public Page(Composite control) {<br>
textField = new Text(control, SWT.NONE);<br>
}<br>
<br>
public void setFocus() {<br>
textField.setFocus();<br>
}<br>
}<br>
<br>
/**<br>
* Non-multiplexing part that relies on the DefaultFocusable
service<br>
* to give focus to its composite.<br>
*/<br>
public class Page2 {<br>
Text textField;<br>
<br>
public Page(Composite control) {<br>
textField = new Text(control, SWT.NONE);<br>
}<br>
}<br>
<br>
/**<br>
* Multiplexing part that contains a Page, a Page2, and a checkbox.<br>
* When the checkbox is selected, the first page will be active.
When the<br>
* checkbox is deselected, the second page will be active. Calling
setFocus<br>
* on the MultiplexingView will always give focus to the active
page.<br>
*/<br>
public class MultiplexingView implements IDisposable, IAdaptable {<br>
private IContainer page1;<br>
private IContainer page2;<br>
<br>
// Helper class that implements the IMultiplexer
interface<br>
private Multiplexer multiplexer = new Multiplexer();<br>
private Button checkBox;<br>
<br>
public MultiplexingView(Composite myControl) {<br>
myControl.setLayout(new RowLayout());<br>
<br>
checkBox = new Button(myControl,
SWT.CHECK);<br>
checkBox.addSelectionListener(new
SelectionAdapter() {<br>
public void
widgetSelected(SelectionEvent e) {<br>
updateActivePart();<br>
}<br>
});<br>
<br>
checkBox.setText("Activate part 1");<br>
<br>
// Create the child controls<br>
IMutableComponentFactory childFactory =
Components.getFactory().createDerivedFactory();<br>
<br>
// Use the CompositeAdapter class we
created earlier in order to provide each page<br>
// with its own control.<br>
childFactory.addComponentInstance(new
CompositeAdapter(myControl));<br>
<br>
page1 =
childFactory.createContainer(Page.class);<br>
page2 =
childFactory.createContainer(Page2.class);<br>
<br>
// Activate the initial part<br>
</code><code> updateActivePart();</code><br>
<code> }<br>
<br>
private final void updateActivePart() {<br>
</code><code> if
(checkBox.getSelection()) {<br>
multiplexer.setCurrent(page1);<br>
} else {<br>
multiplexer.setCurrent(page2);<br>
}</code><br>
<code> }<br>
<br>
/**<br>
* In order to indicate that this is a
multiplexing part, we need to implement<br>
* the IMultiplexer adapter. We simply redirect
to the multiplexer object.<br>
*/<br>
public Object getAdapter(Class adapterType) {<br>
if (adapterType == IMultiplexer.class) {<br>
return multiplexer;<br>
}<br>
}<br>
<br>
/**<br>
* Release any resources allocated in the
constructor<br>
*/<br>
public void dispose() {<br>
page1.dispose();<br>
page2.dispose();<br>
}<br>
}</code><br>
</div>
<br>
Notice that MultiplexingView itself doesn't know about the IFocusable
interface, however if we were to create a MultiplexingView and ask its
container for an IFocusable interface, we would get an interface that
behaves as expected. <br>
<br>
<div style="margin-left: 40px;"><code></code><code>// Stupid example
that creates a MultiplexingView, gives it focus, then destroys it<br>
<br>
// Create a MultiplexingView</code><br>
<code>IMutableComponentFactory childFactory =
Components.getFactory().createDerivedFactory();<br>
</code><code>childFactory.addComponentInstance(new
CompositeAdapter(myControl));<br>
<br>
IContainer viewContainer =
childFactory.createContainer(MultiplexingView.class);<br>
<br>
// Give focus to the active part<br>
IFocusable focusable = viewContainer.getAdapter(IFocusable.class);<br>
focusable.setFocus();<br>
</code><code></code><code><br>
// Destroy the view<br>
viewContainer.dispose();<br>
</code><code></code></div>
<code> <br>
</code>This example also shows how the same service can work for both
multiplexing and non-multiplexing parts. The Page2 class doesn't
implement IFocusable or IMultiplexing part, but we are still able to
ask for an IFocusable interface and use it to give focus to the part.<br>
<h1><a class="mozTocH1" name="mozTocId707022"></a>3.0 Views and Editors
as Components</h1>
TODO<br>
<br>
<br>
</body>
</html>
| help@eclipse.org | ViewVC Help |
| Powered by ViewVC 1.0.3 |
