platform-ui-home/components-proposal/ComponentFrameworkProposal.html
Parent Directory
|
Revision Log
Revision 1.1 - (view) (download) (as text)
| 1 : | sxenos | 1.1 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
| 2 : | <html> | ||
| 3 : | <head> | ||
| 4 : | <meta content="text/html; charset=ISO-8859-1" | ||
| 5 : | http-equiv="content-type"> | ||
| 6 : | <title>Component Framework proposal</title> | ||
| 7 : | </head> | ||
| 8 : | <body> | ||
| 9 : | <span style="font-weight: bold;"></span> | ||
| 10 : | <br> | ||
| 11 : | <div style="text-align: center;"><font size="+3"><span | ||
| 12 : | style="font-weight: bold;">Component Framework Proposal</span></font><br> | ||
| 13 : | </div> | ||
| 14 : | <br> | ||
| 15 : | <div style="text-align: center;">Revision 1.0.3<br> | ||
| 16 : | By Stefan Xenos and Nick Edgar<br> | ||
| 17 : | Last modified 2004/11/02<br> | ||
| 18 : | </div> | ||
| 19 : | <br> | ||
| 20 : | <br> | ||
| 21 : | <br> | ||
| 22 : | <font size="+3"><span style="font-weight: bold;">Table of Contents</span></font><br> | ||
| 23 : | <ul id="mozToc"> | ||
| 24 : | <!--mozToc h1 1 h2 2 h3 3 h4 4 h5 5 h6 6--><li><a href="#mozTocId511305">1.0 | ||
| 25 : | Introduction | ||
| 26 : | </a> | ||
| 27 : | <ul> | ||
| 28 : | <li><a href="#mozTocId29093">1.1 Robustness / leak proofing | ||
| 29 : | </a></li> | ||
| 30 : | <li><a href="#mozTocId223982">1.2 Nesting of components</a></li> | ||
| 31 : | <li><a href="#mozTocId634024">1.3 Creating parts outside the | ||
| 32 : | workbench</a></li> | ||
| 33 : | <li><a href="#mozTocId800379">1.4 Scalability</a></li> | ||
| 34 : | <li><a href="#mozTocId866479">1.5 Ease of use | ||
| 35 : | </a></li> | ||
| 36 : | </ul> | ||
| 37 : | </li> | ||
| 38 : | <li><a href="#mozTocId416167">2.0 Component Framework | ||
| 39 : | </a> | ||
| 40 : | <ul> | ||
| 41 : | <li><a href="#mozTocId489102">2.1 Instantiating a view | ||
| 42 : | </a></li> | ||
| 43 : | <li><a href="#mozTocId717633">2.2 Instantiating components in | ||
| 44 : | general | ||
| 45 : | </a></li> | ||
| 46 : | <li><a href="#mozTocId450231">2.3 Creating dependent components | ||
| 47 : | on demand </a></li> | ||
| 48 : | <li><a href="#mozTocId928619">2.4 Declaring Global Services | ||
| 49 : | </a></li> | ||
| 50 : | <li><a href="#mozTocId749133">2.5 Using services</a></li> | ||
| 51 : | <li><a href="#mozTocId705695">2.6 Lifecycle</a></li> | ||
| 52 : | <li><a href="#mozTocId543223">2.7 Dynamic Services</a></li> | ||
| 53 : | <li><a href="#mozTocId886834">2.8 Optional Services</a></li> | ||
| 54 : | <li><a href="#mozTocId163729">2.9 Multiplexing services</a></li> | ||
| 55 : | </ul> | ||
| 56 : | </li> | ||
| 57 : | <li><a href="#mozTocId707022">3.0 Views and Editors as Components</a></li> | ||
| 58 : | </ul> | ||
| 59 : | <br> | ||
| 60 : | <h1><a class="mozTocH1" name="mozTocId511305"></a>1.0 Introduction<br> | ||
| 61 : | </h1> | ||
| 62 : | This proposal outlines a framework for managing complex executable | ||
| 63 : | extensions. This framework is a first step toward allowing views and | ||
| 64 : | editors to be combined recursively, and created inside arbitrary SWT | ||
| 65 : | composites. Although it is intended for creating reusable UI | ||
| 66 : | components, the framework is useful for any extension point that | ||
| 67 : | creates complicated Java objects. For this reason, we will refer to the | ||
| 68 : | objects being created as "components" even though our components are | ||
| 69 : | typically views and editors.<br> | ||
| 70 : | <br> | ||
| 71 : | This proposal is broken into three parts. The introduction describes | ||
| 72 : | the motivation for creating the component framework and its | ||
| 73 : | requirements. The second section describes the component framework in | ||
| 74 : | itself, which could be used for any executable extension point. The | ||
| 75 : | third section describes how the workbench will use the component | ||
| 76 : | framework for views and editors.<br> | ||
| 77 : | <br> | ||
| 78 : | Primary goals:<br> | ||
| 79 : | <ul> | ||
| 80 : | <li>Robustness / leak proofing</li> | ||
| 81 : | <li>Nesting of components</li> | ||
| 82 : | <li>Allow components to be reused outside the workbench</li> | ||
| 83 : | <li>Scalability (allow an open-ended set of components)</li> | ||
| 84 : | <li>API versioning<br> | ||
| 85 : | </li> | ||
| 86 : | <li>Ease of use</li> | ||
| 87 : | </ul> | ||
| 88 : | <h2><a class="mozTocH2" name="mozTocId29093"></a>1.1 Robustness / leak | ||
| 89 : | proofing<br> | ||
| 90 : | </h2> | ||
| 91 : | Much of the Eclipse API is currently accessed through singleton | ||
| 92 : | objects. This makes it easy for a view or editor to leak listeners, | ||
| 93 : | fail to clean up reference counts, leak OS resources, etc. since there | ||
| 94 : | is no way of tracking which resources were allocated by a particular | ||
| 95 : | view. This problem would be reduced if views and editors were more like | ||
| 96 : | mini-applications. The view or editor would access the rest of the | ||
| 97 : | world through a set of local services. When the view or editor is | ||
| 98 : | destroyed so would all of the services allocated for it. This gives | ||
| 99 : | each service a chance to clean up after itself. <br> | ||
| 100 : | <br> | ||
| 101 : | For example, instead of reaching into a global | ||
| 102 : | preference store, a view or editor could access all preferences through | ||
| 103 : | a local preference | ||
| 104 : | service. A unique instance of the local preference service would be | ||
| 105 : | created for each view, and would be disposed with the view. When the | ||
| 106 : | preference service is destroyed it would clear its listener list, | ||
| 107 : | ensuring that the view will not leak any global listeners. This example | ||
| 108 : | shows what we mean by a component. Essentially, a | ||
| 109 : | component is an object that communicates with the rest of the | ||
| 110 : | application through a set of interfaces given to it in its constructor.<br> | ||
| 111 : | <span style="font-weight: bold;"></span> | ||
| 112 : | <h2><a class="mozTocH2" name="mozTocId223982"></a>1.2 Nesting of | ||
| 113 : | components</h2> | ||
| 114 : | There is demand for the ability to embed views and editors inside one | ||
| 115 : | another. Some examples:<br> | ||
| 116 : | <ul> | ||
| 117 : | <li>An XML editor might embed the properties view</li> | ||
| 118 : | <li>A refactoring wizard might include a source editor</li> | ||
| 119 : | <li>A plugin may wish to create a set of pluggable UI components that | ||
| 120 : | are not views or editors themselves, but can be used inside any view or | ||
| 121 : | editor</li> | ||
| 122 : | <li>Various workbench objects (like the PartSashContainer that | ||
| 123 : | handles the layout of docked parts within the workbench) could be | ||
| 124 : | exposed as API<br> | ||
| 125 : | </li> | ||
| 126 : | </ul> | ||
| 127 : | Many downstream plugins have solved these problems by creating their | ||
| 128 : | own frameworks for reusable UI components. Unfortunately, only works | ||
| 129 : | for specific views and editors, does not encourage interoperability | ||
| 130 : | between plugins that have adopted different frameworks, and requires a | ||
| 131 : | lot of work. The goal here is to adopt a framework in the workbench | ||
| 132 : | itself that allows all editors, views, and other workbench objects to | ||
| 133 : | be easily nested.<br> | ||
| 134 : | <h2><a class="mozTocH2" name="mozTocId634024"></a>1.3 Creating parts | ||
| 135 : | outside the workbench</h2> | ||
| 136 : | It should be possible to instantiate views and editors outside the | ||
| 137 : | workbench. Some examples:<br> | ||
| 138 : | <ul> | ||
| 139 : | <li>Unit-test editors and views by instantiating them within a JUnit | ||
| 140 : | test suite.</li> | ||
| 141 : | <li>Create an RCP application that does not depend on the workbench | ||
| 142 : | but includes view-like pluggable parts | ||
| 143 : | that could also be used as views within Eclipse</li> | ||
| 144 : | </ul> | ||
| 145 : | <h2><a class="mozTocH2" name="mozTocId800379"></a>1.4 Scalability</h2> | ||
| 146 : | The workbench currently offers a closed set of services to parts. This | ||
| 147 : | forces parts to reach to global objects whenever they need something | ||
| 148 : | that isn't available from their site. It should be possible for any | ||
| 149 : | plugin to contribute to the set of local API available to a part, and | ||
| 150 : | it | ||
| 151 : | should be possible to instantiate a part even if its parent doesn't | ||
| 152 : | know about all of a part's dependencies.<br> | ||
| 153 : | <h2><a class="mozTocH2" name="mozTocId866479"></a>1.5 Ease of use<br> | ||
| 154 : | </h2> | ||
| 155 : | Views and editors currently have a complicated lifecycle that must be | ||
| 156 : | managed by the workbench. This complexity should not be exposed to | ||
| 157 : | client code. <br> | ||
| 158 : | <ul> | ||
| 159 : | <li>It should only require one method call to create a component and | ||
| 160 : | one method call to destroy it.</li> | ||
| 161 : | <li>There should not be unnecessary duplication between XML and java | ||
| 162 : | code.</li> | ||
| 163 : | <li>Components should not | ||
| 164 : | be responsible for dealing with error conditions (such as missing | ||
| 165 : | dependencies) that can be detected by the framework.</li> | ||
| 166 : | <li>Components should not need to implement interfaces they don't | ||
| 167 : | care about.</li> | ||
| 168 : | <li>A parent context should not need to provide child components with | ||
| 169 : | interfaces it doesn't care about.<span style="font-weight: bold;"></span></li> | ||
| 170 : | </ul> | ||
| 171 : | <h1><a class="mozTocH1" name="mozTocId416167"></a>2.0 Component | ||
| 172 : | Framework<br> | ||
| 173 : | </h1> | ||
| 174 : | Components are pluggable objects that are build using constructor | ||
| 175 : | injection. Constructor injection means that the framework looks at the | ||
| 176 : | object's constructor to determine what it needs to do to build that | ||
| 177 : | object.<br> | ||
| 178 : | <br> | ||
| 179 : | Components:<br> | ||
| 180 : | <ul> | ||
| 181 : | <li>Have exactly one constructor</li> | ||
| 182 : | <li>Take zero or more other components as arguments to their | ||
| 183 : | constructor</li> | ||
| 184 : | <li>Are fully initialized by their constructor</li> | ||
| 185 : | </ul> | ||
| 186 : | Components do not:<br> | ||
| 187 : | <ul> | ||
| 188 : | <li>Take arrays or primitives as arguments to their constructor</li> | ||
| 189 : | <li>Take more than one argument of the same type in their constructor<br> | ||
| 190 : | </li> | ||
| 191 : | <li>Need to support any particular base class or interface</li> | ||
| 192 : | </ul> | ||
| 193 : | Any class with these properties can be used as a component. Since | ||
| 194 : | components are plain-old-java-objects, they do not need to depend on | ||
| 195 : | the component framework. Components are fully initialized by | ||
| 196 : | their constructor, meaning it is never necessary | ||
| 197 : | to call an initialize method or any combination of set methods after | ||
| 198 : | constructing the object. Components never accept null as an argument | ||
| 199 : | to their constructor.<br> | ||
| 200 : | <br> | ||
| 201 : | For example, if a view could be created as a component it might look | ||
| 202 : | like this:<br> | ||
| 203 : | <br> | ||
| 204 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 205 : | <code> * "Hello world" view using the service framework</code><br> | ||
| 206 : | <code> */</code><br> | ||
| 207 : | <code>public class HelloWorldView {</code><br> | ||
| 208 : | <code> public HelloWorldView(Composite parent) {</code><br> | ||
| 209 : | <code> Label helloWorld = new | ||
| 210 : | Label(parent, SWT.NONE);</code><br> | ||
| 211 : | <code> | ||
| 212 : | helloWorld.setText("Hello | ||
| 213 : | world");</code><br> | ||
| 214 : | <code> }</code><br> | ||
| 215 : | <code>}</code><br> | ||
| 216 : | <code></code></div> | ||
| 217 : | <code><br> | ||
| 218 : | </code>The HelloWorldView component depends on one service: a Composite | ||
| 219 : | created for it by whoever instantiated the view. Compare this with the | ||
| 220 : | same view written using the existing API:<br> | ||
| 221 : | <br> | ||
| 222 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 223 : | <code> * "hello world" view using the Eclipse 3.0 API.</code><br> | ||
| 224 : | <code> */</code><br> | ||
| 225 : | <code>public class HelloWorldView extends ViewPart {</code><br> | ||
| 226 : | <code> public void createPartControl(Composite | ||
| 227 : | parent) {</code><br> | ||
| 228 : | <code></code><code> Label | ||
| 229 : | helloWorld = new Label(parent, SWT.NONE);</code><br> | ||
| 230 : | <code> | ||
| 231 : | helloWorld.setText("Hello | ||
| 232 : | world");</code><code> </code><br> | ||
| 233 : | <code> }</code><br> | ||
| 234 : | <code></code><br> | ||
| 235 : | <code> public void setFocus() {</code><br> | ||
| 236 : | <code> }</code><br> | ||
| 237 : | <code>}</code><br> | ||
| 238 : | </div> | ||
| 239 : | <br> | ||
| 240 : | The main difference is that the component version does not require the | ||
| 241 : | ViewPart base class and is fully initialized after construction. In | ||
| 242 : | both cases, the extension point markup would look like this:<br> | ||
| 243 : | <br> | ||
| 244 : | <div style="margin-left: 40px;"><code><extension | ||
| 245 : | point="org.eclipse.ui.views"></code><br> | ||
| 246 : | <code> <view</code><br> | ||
| 247 : | <code> name="Title Test View"</code><br> | ||
| 248 : | <code> icon="icons\view.gif"</code><br> | ||
| 249 : | <code> | ||
| 250 : | class="org.eclipse.ui.mytest.HelloWorldView"</code><br> | ||
| 251 : | <code> id="</code><code>org.eclipse.ui.mytest.HelloWorldView</code><code>ID"></code><br> | ||
| 252 : | <code> </view></code><br> | ||
| 253 : | <code></extension></code><br> | ||
| 254 : | </div> | ||
| 255 : | <br> | ||
| 256 : | <h2><a class="mozTocH2" name="mozTocId489102"></a>2.1 Instantiating a | ||
| 257 : | view<br> | ||
| 258 : | </h2> | ||
| 259 : | Components are instantiated using factories. For the moment, let's | ||
| 260 : | focus on views before we explore components in general. Views could be | ||
| 261 : | created using a method on IWorkbenchPage that would look something like | ||
| 262 : | this. Note that this example is only intended to illustrate the general | ||
| 263 : | idea -- the actual protocol for creating views is likely to change.<br> | ||
| 264 : | <br> | ||
| 265 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 266 : | <code> * Creates a view of the given type inside the given | ||
| 267 : | composite. | ||
| 268 : | The caller must dispose the container once they are done with it.</code><br> | ||
| 269 : | <code> *</code><br> | ||
| 270 : | <code> * @param viewId id of the view extension to use</code><br> | ||
| 271 : | <code> * @param parentComposite parent composite for the view</code><br> | ||
| 272 : | <code> * @return an IContainer that contains all components needed | ||
| 273 : | for the view</code><br> | ||
| 274 : | <code> */</code><br> | ||
| 275 : | <code>IContainer createView(String viewId, Composite parentComposite);</code><br> | ||
| 276 : | <code></code></div> | ||
| 277 : | <code></code><br> | ||
| 278 : | <br> | ||
| 279 : | This creates a new instance of the view in the given composite. Notice | ||
| 280 : | that the factory method doesn't return an instance of the view itself | ||
| 281 : | but an instance of IContainer, which looks something like this:<br> | ||
| 282 : | <br> | ||
| 283 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 284 : | <code> * Main interface to a component.</code><br> | ||
| 285 : | <code> */</code><br> | ||
| 286 : | <code>public interface IContainer extends IAdaptable {</code><br> | ||
| 287 : | <code></code> public void dispose();<br> | ||
| 288 : | }<br> | ||
| 289 : | </div> | ||
| 290 : | <br> | ||
| 291 : | This interface wraps the component and all of its dependencies. Once | ||
| 292 : | we've obtained an IContainer handle, we are obligated to dispose it | ||
| 293 : | when we are done with it. All access to the component is done through | ||
| 294 : | adapters, which insulates | ||
| 295 : | the application from changes in individual views. For example, if a | ||
| 296 : | view that previously implemented the IViewInterface1 migrates to using | ||
| 297 : | the newer IViewInterface2, its container will continue to work as long | ||
| 298 : | as someone has provided an adapter between the two versions of | ||
| 299 : | IViewInterface. The getAdapter() method on IComponent searches for | ||
| 300 : | adapters in the following order:<br> | ||
| 301 : | <br> | ||
| 302 : | 1. If the component itself implements the adapter type, it returns the | ||
| 303 : | component.<br> | ||
| 304 : | 2. If the component itself implements IAdapter, and the component's | ||
| 305 : | getAdapter(...) method returns non-null, we return that adapter.<br> | ||
| 306 : | 3. If the adapter manager has an adapter between the component and the | ||
| 307 : | adapter type, we return that adapter.<br> | ||
| 308 : | 4. If the container's factory can construct a component of the | ||
| 309 : | requested type, we | ||
| 310 : | create that component, add it as a local dependency to the IContainer, | ||
| 311 : | and | ||
| 312 : | return it.<br> | ||
| 313 : | 5. Return null<br> | ||
| 314 : | <br> | ||
| 315 : | For example, it would be possible to create our HelloWorldView (above) | ||
| 316 : | in a modal dialog like this:<br> | ||
| 317 : | <br> | ||
| 318 : | <div style="margin-left: 40px;"><code>void | ||
| 319 : | createHelloWorldViewInADialog(IWorkbenchPage page) {</code><br> | ||
| 320 : | <code> Display display = Display.getDefault()</code><br> | ||
| 321 : | <code> Shell shell = new Shell(Display.getDefault());</code><br> | ||
| 322 : | <code></code><br> | ||
| 323 : | <code> shell.setLayout(new FillLayout());</code><br> | ||
| 324 : | <code></code><br> | ||
| 325 : | <code> // Create the view and its widgets</code><br> | ||
| 326 : | <code> IContainer myView = | ||
| 327 : | workbenchPage.createView("org.eclipse.ui.mytest.HelloWorldViewID", | ||
| 328 : | shell);</code><br> | ||
| 329 : | <code></code><br> | ||
| 330 : | <code> shell.open();</code><br> | ||
| 331 : | <code></code><br> | ||
| 332 : | <code> while (!shell.isDisposed()) {</code><br> | ||
| 333 : | <code> if (!display.readAndDispatch ()) | ||
| 334 : | display.sleep ();</code><br> | ||
| 335 : | <code> }</code><br> | ||
| 336 : | <code></code><br> | ||
| 337 : | <code> // Dispose the view</code><br> | ||
| 338 : | <code> myView.dispose();</code><br> | ||
| 339 : | <code>}</code><br> | ||
| 340 : | <code></code></div> | ||
| 341 : | <h2><a class="mozTocH2" name="mozTocId717633"></a>2.2 Instantiating | ||
| 342 : | components in general<br> | ||
| 343 : | </h2> | ||
| 344 : | All components are constructed using an IContainerFactory, however most | ||
| 345 : | component-based extension points will provide some | ||
| 346 : | sort of convenience method to wrap their IContainerFactory. The | ||
| 347 : | IWorkbenchPage.createView method in the | ||
| 348 : | previous section is a convenience method for the <span | ||
| 349 : | style="font-style: italic;">org.eclipse.ui.views </span>extension | ||
| 350 : | point.<br style="font-style: italic;"> | ||
| 351 : | <br> | ||
| 352 : | IContainerFactory looks like this:<br> | ||
| 353 : | <br> | ||
| 354 : | <div style="margin-left: 40px;"><code>/**<br> | ||
| 355 : | * Factory for IContainer instances. The default factory is | ||
| 356 : | returned by<br> | ||
| 357 : | * Components.getFactory(). Clients wishing to implement their own | ||
| 358 : | specialized <br> | ||
| 359 : | * factories should call createDerivedFactory() to get access to a | ||
| 360 : | factory<br> | ||
| 361 : | * whose behavior can be modified programmatically. Not intended | ||
| 362 : | to be <br> | ||
| 363 : | * implemented by clients. <br> | ||
| 364 : | * <br> | ||
| 365 : | * @since 3.1<br> | ||
| 366 : | */<br> | ||
| 367 : | public interface IContainerFactory {<br> | ||
| 368 : | <br> | ||
| 369 : | /**<br> | ||
| 370 : | * Creates and returns a new IContainer | ||
| 371 : | instance, given the <br> | ||
| 372 : | * implementation class for its component. The | ||
| 373 : | caller MUST call IContainer.dispose() <br> | ||
| 374 : | * when it is done with the component. The | ||
| 375 : | factory does not need any prior<br> | ||
| 376 : | * knowledge of the component class be<br> | ||
| 377 : | * <br> | ||
| 378 : | * @param componentImplementation concrete | ||
| 379 : | class to be instantiated by the factory. The class<br> | ||
| 380 : | * | ||
| 381 : | must be a valid component (it must have exactly one constructor which | ||
| 382 : | only<br> | ||
| 383 : | * | ||
| 384 : | references other component interfaces known to this factory).<br> | ||
| 385 : | * @return a newly constructed | ||
| 386 : | <code>IContainer</code> instance<br> | ||
| 387 : | * @throws MissingDependencyException if the | ||
| 388 : | requested component depends on another component that<br> | ||
| 389 : | | ||
| 390 : | * cannot be constructed | ||
| 391 : | by this factory<br> | ||
| 392 : | * @throws CoreException if there is a more | ||
| 393 : | permanent problem with using the given class as <br> | ||
| 394 : | | ||
| 395 : | * a component <br> | ||
| 396 : | * @since 3.1<br> | ||
| 397 : | */<br> | ||
| 398 : | public IContainer createContainer(Class | ||
| 399 : | componentImplementation) throws MissingDependencyException, | ||
| 400 : | CoreException;<br> | ||
| 401 : | <br> | ||
| 402 : | /**<br> | ||
| 403 : | * Creates a specialization of this factory. By | ||
| 404 : | default, the specialized<br> | ||
| 405 : | * factory will have the same behavior as its | ||
| 406 : | parent. However, the derived<br> | ||
| 407 : | * factory can add, or override the | ||
| 408 : | implementation for any components.<br> | ||
| 409 : | * <br> | ||
| 410 : | * @return new factory instance that allows | ||
| 411 : | individual components to be overridden.<br> | ||
| 412 : | * By default, the derived factory will | ||
| 413 : | delegate all of its behavior to the receiver.<br> | ||
| 414 : | * Changes in the receiver will affect the | ||
| 415 : | derived factory.<br> | ||
| 416 : | * @since 3.1<br> | ||
| 417 : | */<br> | ||
| 418 : | public IMutableContainerFactory | ||
| 419 : | createDerivedFactory();<br> | ||
| 420 : | }</code><code></code><br> | ||
| 421 : | <code></code></div> | ||
| 422 : | <code></code><br> | ||
| 423 : | Notice that createComponent throws two types of exception. A | ||
| 424 : | CoreException indicates a problem with the component itself, and a | ||
| 425 : | MissingDependencyException indicates that some other component or | ||
| 426 : | adapter could not be found. This can help locate the cause of problems, | ||
| 427 : | and a robust extension point may want to disable components that throw | ||
| 428 : | CoreExceptions.<br> | ||
| 429 : | <br> | ||
| 430 : | All arguments to a component's constructor are provided by its factory. | ||
| 431 : | Different | ||
| 432 : | factories know how to create different types of components or will | ||
| 433 : | provide | ||
| 434 : | different implementations for the same components. All factories are | ||
| 435 : | derived from the root factory, which is returned by | ||
| 436 : | Components.getFactory(). A plugin can create derived factories to | ||
| 437 : | supply services to one specific extension point. It is also possible to | ||
| 438 : | create a derived factory in order to pass information to the | ||
| 439 : | constructor of one specific object, as we will do in this example. In | ||
| 440 : | our case, we will create a container factory to pass a specific | ||
| 441 : | Composite into the constructor of HelloWorldView. The code looks like | ||
| 442 : | this:<br> | ||
| 443 : | <code></code><code><br> | ||
| 444 : | </code> | ||
| 445 : | <div style="margin-left: 40px;"><code>// Get the global factory</code><br> | ||
| 446 : | <code>IContainerFactory viewFactory = | ||
| 447 : | Components.getFactory();</code><br> | ||
| 448 : | <code></code><br> | ||
| 449 : | <code>// Create the Composite for the view</code><br> | ||
| 450 : | <code></code><code>Composite viewComposite = new | ||
| 451 : | Composite(parentComposite, SWT.NONE);</code><br> | ||
| 452 : | <code>viewComposite.setLayout(new FillLayout();</code><br> | ||
| 453 : | <code></code><br> | ||
| 454 : | <code>// Create a specialized factory that knows about the view's | ||
| 455 : | composite and plugin bundle</code><br> | ||
| 456 : | <code>IMutableContainerFactory derivedFactory = | ||
| 457 : | viewFactory.createDerivedFactory();</code><br> | ||
| 458 : | <code></code><br> | ||
| 459 : | <code>// Add the view's composite as a service instance</code><br> | ||
| 460 : | <code>derivedFactory.addComponentInstance(viewComposite);</code><br> | ||
| 461 : | <code></code><br> | ||
| 462 : | <code>// Add the view's plugin bundle as a service instance (not | ||
| 463 : | required in this example, but this</code><br> | ||
| 464 : | <code>// is recommended practise for any component created from an | ||
| 465 : | extension point).</code><br> | ||
| 466 : | <code>derivedFactory.addComponentInstance(pluginBundle);</code><br> | ||
| 467 : | <code></code><br> | ||
| 468 : | <code>// Create the view. Provide its constructor and some | ||
| 469 : | context (the page that created it).</code><br> | ||
| 470 : | <code>IContainer view = | ||
| 471 : | derivedFactory.createContainer(HelloWorldView.class);</code><br> | ||
| 472 : | <code></code><br> | ||
| 473 : | <code>// Do something with the view</code><br> | ||
| 474 : | <code>// ...</code><br> | ||
| 475 : | <code></code><br> | ||
| 476 : | <code>// Now clean up</code><br> | ||
| 477 : | <code>view.dispose();</code><br> | ||
| 478 : | <code>viewComposite.dispose();</code><br> | ||
| 479 : | <code></code></div> | ||
| 480 : | <code><br> | ||
| 481 : | </code><br> | ||
| 482 : | This code will work, but it has two problems:<br> | ||
| 483 : | <br> | ||
| 484 : | 1. Even if HelloWorldView didn't require a Composite, we would still | ||
| 485 : | have created one (which is wasteful).<br> | ||
| 486 : | 2. We need to manually dispose the view's composite after we're done | ||
| 487 : | with it. This defeats the point of IComponent.dispose(), which is | ||
| 488 : | supposed to clean up all of the component's dependencies automatically.<br> | ||
| 489 : | <h2><a class="mozTocH2" name="mozTocId450231"></a>2.3 Creating | ||
| 490 : | dependent components on demand </h2> | ||
| 491 : | Rather than managing the view's Composite ourselves, we could supply a | ||
| 492 : | factory that knows how to create and destroy Composites as needed. Such | ||
| 493 : | a factory would look like this:<br> | ||
| 494 : | <br> | ||
| 495 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 496 : | <code> * ServiceAdapter for creating and managing SWT Composites | ||
| 497 : | as services.</code><br> | ||
| 498 : | <code> */</code><br> | ||
| 499 : | <code>public class CompositeFactory extends ComponentAdapter {</code><br> | ||
| 500 : | <code></code><br> | ||
| 501 : | <code> private Composite parent;</code><br> | ||
| 502 : | <code> </code><br> | ||
| 503 : | <code> /**</code><br> | ||
| 504 : | <code> * The SWT composites created by this | ||
| 505 : | factory will all be children of the given</code><br> | ||
| 506 : | <code> * composite.</code><br> | ||
| 507 : | <code> */</code><br> | ||
| 508 : | <code> public CompositeFactory(Composite parent) {</code><br> | ||
| 509 : | <code> this.parent = parent;</code><br> | ||
| 510 : | <code> }</code><br> | ||
| 511 : | <code> </code><br> | ||
| 512 : | <code> protected Object create(IAdaptable | ||
| 513 : | availableServices) throws CoreException {</code><br> | ||
| 514 : | <code> // Create a new Composite | ||
| 515 : | which will be used as a service.</code><br> | ||
| 516 : | <code> // If we needed any | ||
| 517 : | additional</code><br> | ||
| 518 : | <code> Composite newChild = | ||
| 519 : | new Composite(parent, SWT.NONE);</code><br> | ||
| 520 : | <code> newChild.setLayout(new | ||
| 521 : | FillLayout());</code><br> | ||
| 522 : | <code> return newChild;</code><br> | ||
| 523 : | <code> }</code><br> | ||
| 524 : | <code></code><br> | ||
| 525 : | <code> protected void dispose(Object service) {</code><br> | ||
| 526 : | <code> | ||
| 527 : | ((Composite)service).dispose();</code><br> | ||
| 528 : | <code> }</code><br> | ||
| 529 : | <code></code><br> | ||
| 530 : | <code> public String getInterfaceName() {</code><br> | ||
| 531 : | <code> // Return the fully | ||
| 532 : | qualified class name of the Composite class. The framework will call</code><br> | ||
| 533 : | <code> // this method to determine | ||
| 534 : | what type of service will be constructed by this factory.</code><br> | ||
| 535 : | <code> return | ||
| 536 : | Composite.class.getName();</code><br> | ||
| 537 : | <code> }</code><br> | ||
| 538 : | <code>}</code><br> | ||
| 539 : | <code></code></div> | ||
| 540 : | <br> | ||
| 541 : | Using CompositeFactory, we could construct HelloWorldView like this:<br> | ||
| 542 : | <br> | ||
| 543 : | <div style="margin-left: 40px;"><code>// Get the global factory</code><br> | ||
| 544 : | <code>IContainerFactory viewFactory = | ||
| 545 : | Components.getFactory();</code><code></code><br> | ||
| 546 : | <code></code><br> | ||
| 547 : | <code>// Create a specialized factory that knows about the view's | ||
| 548 : | composite and plugin bundle</code><br> | ||
| 549 : | <code>IMutableContainerFactory derivedFactory = | ||
| 550 : | viewFactory.createDerivedFactory();</code><br> | ||
| 551 : | <code></code><br> | ||
| 552 : | <code>// Add the view's composite as a service instance</code><br> | ||
| 553 : | <code>derivedFactory.addComponentFactory(new | ||
| 554 : | CompositeFactory(parentComposite));</code><br> | ||
| 555 : | <code></code><br> | ||
| 556 : | <code>// Add the view's plugin bundle as a service instance (not | ||
| 557 : | required in this example, but this</code><br> | ||
| 558 : | <code>// is recommended practise for any component created from an | ||
| 559 : | extension point).</code><br> | ||
| 560 : | <code>derivedFactory.addComponentInstance(pluginBundle);</code><br> | ||
| 561 : | <code></code><br> | ||
| 562 : | <code>// Create the view. Provide its constructor and some | ||
| 563 : | context (the page that created it).</code><br> | ||
| 564 : | <code>IContainer view = | ||
| 565 : | derivedFactory.createComponent(HelloWorldView.class);</code><br> | ||
| 566 : | <code></code><br> | ||
| 567 : | <code>// Do something with the view</code><br> | ||
| 568 : | <code>// ...</code><br> | ||
| 569 : | <code></code><br> | ||
| 570 : | <code>// Now clean up</code><br> | ||
| 571 : | <code>view.dispose();</code><br> | ||
| 572 : | <code></code></div> | ||
| 573 : | <code> | ||
| 574 : | </code><br> | ||
| 575 : | The composite will now be allocated as needed and disposed inside the | ||
| 576 : | call to view.dispose().<br> | ||
| 577 : | <br> | ||
| 578 : | <code></code> | ||
| 579 : | <h2><a class="mozTocH2" name="mozTocId928619"></a>2.4 Declaring Global | ||
| 580 : | Services<br> | ||
| 581 : | </h2> | ||
| 582 : | Services are a type of component that can be used by other components. | ||
| 583 : | Services are registered through the <span style="font-style: italic;">org.eclipse.ui.component.service | ||
| 584 : | </span>extension | ||
| 585 : | point. Services are global in the sense that they are created by | ||
| 586 : | factories | ||
| 587 : | that are visible everywhere, however each instance of a services is | ||
| 588 : | associated with a specific IContainer. Each service implements a | ||
| 589 : | service interface. Other components can use the service interface in | ||
| 590 : | their constructor. When they do so, a new instance of the service will | ||
| 591 : | be created for that component which will be managed by the same | ||
| 592 : | IContainer. <br> | ||
| 593 : | <br> | ||
| 594 : | Each component supplies an implementation and an interface. Here is an | ||
| 595 : | example service interface:<br> | ||
| 596 : | <code><br> | ||
| 597 : | </code> | ||
| 598 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 599 : | <code> * Service interface: </code><br> | ||
| 600 : | <code> *</code><br> | ||
| 601 : | <code> * Provides a context for reporting and logging exceptions.</code><br> | ||
| 602 : | <code> */</code><br> | ||
| 603 : | <code>public interface IErrorContext {</code><br> | ||
| 604 : | <code> /**</code><br> | ||
| 605 : | <code> * Create an IStatus error describing the | ||
| 606 : | given | ||
| 607 : | throwable</code><br> | ||
| 608 : | <code> */</code><br> | ||
| 609 : | <code> public IStatus createStatus(Throwable t);</code><br> | ||
| 610 : | <code></code><br> | ||
| 611 : | <code> /**</code><br> | ||
| 612 : | <code> * Create an IStatus message with the | ||
| 613 : | given | ||
| 614 : | severity, message, and (optional) throwable</code><br> | ||
| 615 : | <code> */</code><br> | ||
| 616 : | <code> public IStatus createStatus(int severity, | ||
| 617 : | String | ||
| 618 : | message, Throwable t);</code><br> | ||
| 619 : | <code></code><br> | ||
| 620 : | <code> /**</code><br> | ||
| 621 : | <code> * Logs an IStatus message</code><br> | ||
| 622 : | <code> */</code><br> | ||
| 623 : | <code> public void log(IStatus status);</code><br> | ||
| 624 : | <code></code><br> | ||
| 625 : | <code> /**</code><br> | ||
| 626 : | <code> * Logs an exception to the system log</code><br> | ||
| 627 : | <code> */</code><br> | ||
| 628 : | <code> public void log(Throwable t);</code><br> | ||
| 629 : | <code>}</code><br> | ||
| 630 : | <code></code></div> | ||
| 631 : | <code><br> | ||
| 632 : | <br> | ||
| 633 : | </code>Here is the associated service implementation:<br> | ||
| 634 : | <code><br> | ||
| 635 : | </code> | ||
| 636 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 637 : | <code> * Service implementation:</code><br> | ||
| 638 : | <code> *</code><br> | ||
| 639 : | <code> * Creates and logs errors in the context of a plugin bundle</code><br> | ||
| 640 : | <code> */</code><br> | ||
| 641 : | <code>public class ErrorContext implements IErrorContext {</code><br> | ||
| 642 : | <code> private Bundle pluginBundle;</code><br> | ||
| 643 : | <code></code><br> | ||
| 644 : | <code> public ExceptionLoggingService(Bundle | ||
| 645 : | pluginBundle) {</code><br> | ||
| 646 : | <code> this.pluginBundle = | ||
| 647 : | pluginBundle;</code><br> | ||
| 648 : | <code> }</code><br> | ||
| 649 : | <code></code><br> | ||
| 650 : | <code> public IStatus createStatus(Throwable t) {</code><br> | ||
| 651 : | <code> String message = t.getMessage();</code><br> | ||
| 652 : | <code> if (message == null) {</code><br> | ||
| 653 : | <code> message = | ||
| 654 : | t.getString();</code><br> | ||
| 655 : | <code> }</code><br> | ||
| 656 : | <code> return createStatus(Status.ERROR, | ||
| 657 : | message, t);</code><br> | ||
| 658 : | <code> }</code><br> | ||
| 659 : | <code></code><br> | ||
| 660 : | <code> public IStatus createStatus(int severity, | ||
| 661 : | String | ||
| 662 : | message, Throwable t) {</code><br> | ||
| 663 : | <code> return new Status(severity, | ||
| 664 : | pluginBundle.getSymbolicName(), Status.OK, message, t);</code><br> | ||
| 665 : | <code> }</code><br> | ||
| 666 : | <code></code><br> | ||
| 667 : | <code> public void log(Throwable t) {</code><br> | ||
| 668 : | <code> log(createStatus(t));</code><br> | ||
| 669 : | <code> }</code><br> | ||
| 670 : | <code></code><br> | ||
| 671 : | <code> public void log(IStatus status) {</code><br> | ||
| 672 : | <code> Plugin plugin = | ||
| 673 : | Platform.getPlugin(pluginBundle.getSymbolicName());</code><br> | ||
| 674 : | <code> plugin.getLog().log(status);</code><br> | ||
| 675 : | <code> }</code><br> | ||
| 676 : | <code>}</code><br> | ||
| 677 : | <code></code></div> | ||
| 678 : | <code></code><br> | ||
| 679 : | Finally, here is the extension markup:<br> | ||
| 680 : | <br> | ||
| 681 : | <div style="margin-left: 40px;"><code><extension | ||
| 682 : | point="org.eclipse.ui.component.service"></code><br> | ||
| 683 : | <code> <service | ||
| 684 : | </code><br> | ||
| 685 : | <code> | ||
| 686 : | class="org.eclipse.ui.workbench.ErrorContext"</code><br> | ||
| 687 : | <code> </code><code>interface="</code><code>org.eclipse.ui.workbench.IErrorContext</code><code>"</code><code>></code><br> | ||
| 688 : | <code> </service></code><br> | ||
| 689 : | <code></extension></code><br> | ||
| 690 : | </div> | ||
| 691 : | <br> | ||
| 692 : | Notice that the extension XML needs to indicate which service interface | ||
| 693 : | is being implemented by the service, but not its dependencies. When it | ||
| 694 : | comes time to create the service, the framework detects that the | ||
| 695 : | ErrorContext needs to be associated with a plugin Bundle by examining | ||
| 696 : | its constructor. This means that if plugin A defines the ErrorContext | ||
| 697 : | service and plugin B defines a view that uses the service, then all of | ||
| 698 : | the status messages constructed by the view will be associated with the | ||
| 699 : | view's own plugin - not the plugin that defined the ErrorContext | ||
| 700 : | service.<br> | ||
| 701 : | <h2><a class="mozTocH2" name="mozTocId749133"></a>2.5 Using services</h2> | ||
| 702 : | This example shows how a component can use a service.<br> | ||
| 703 : | <br> | ||
| 704 : | <div style="margin-left: 40px;"><code>public class MyView {</code><br> | ||
| 705 : | <code> private IErrorContext errorContext;</code><br> | ||
| 706 : | <code></code><br> | ||
| 707 : | <code> public MyView(</code><code>Composite parent, </code><code>IErrorContext | ||
| 708 : | errorContext) {</code><br> | ||
| 709 : | <code> this.errorContext = errorContext;</code><br> | ||
| 710 : | <code></code><br> | ||
| 711 : | <code></code><code> Button errorButton = | ||
| 712 : | new | ||
| 713 : | Button(parent, SWT.PUSH);</code><br> | ||
| 714 : | <code> errorButton.setText("Log an | ||
| 715 : | exception");</code><br> | ||
| 716 : | <code> </code><br> | ||
| 717 : | <code> | ||
| 718 : | errorButton.addSelectionListener(new | ||
| 719 : | SelectionAdapter() {</code><br> | ||
| 720 : | <code> public void | ||
| 721 : | widgetSelected(SelectionEvent e) {</code><br> | ||
| 722 : | <code> | ||
| 723 : | | ||
| 724 : | try {</code><br> | ||
| 725 : | <code> | ||
| 726 : | | ||
| 727 : | // Throw a NPE</code><br> | ||
| 728 : | <code> | ||
| 729 : | | ||
| 730 : | String myString = null;</code><br> | ||
| 731 : | <code> | ||
| 732 : | | ||
| 733 : | String bogusCode = myString.substring(10, 30);</code><br> | ||
| 734 : | <code> | ||
| 735 : | | ||
| 736 : | } catch (Exception e) {</code><br> | ||
| 737 : | <code> | ||
| 738 : | | ||
| 739 : | // Log the NPE using the error context service</code><br> | ||
| 740 : | <code> | ||
| 741 : | errorContext.log(e);</code><br> | ||
| 742 : | <code> | ||
| 743 : | }</code><br> | ||
| 744 : | <code> }</code><br> | ||
| 745 : | <code> });</code><br> | ||
| 746 : | <code></code><code> }</code><br> | ||
| 747 : | <code></code><code>}</code><br> | ||
| 748 : | <code></code></div> | ||
| 749 : | <code><br> | ||
| 750 : | </code>This view creates a button which, when pressed, will throw a NPE | ||
| 751 : | and log it. The status message will be constructed and logged using the | ||
| 752 : | IErrorContext service we defined above.<br> | ||
| 753 : | <br> | ||
| 754 : | <h2><a class="mozTocH2" name="mozTocId705695"></a>2.6 Lifecycle</h2> | ||
| 755 : | Many components need to do explicit cleanup. Components | ||
| 756 : | that need to perform cleanup will implement the | ||
| 757 : | IDisposable interface. For example, the following component attaches a | ||
| 758 : | listener to a global preference store and detaches it when done.<br> | ||
| 759 : | <br> | ||
| 760 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 761 : | <code> * Provides access to a plugin's preference store in a | ||
| 762 : | manner that | ||
| 763 : | prevents listener leaks.</code><br> | ||
| 764 : | <code> */</code><br> | ||
| 765 : | <code>public interface IPreferences {</code><br> | ||
| 766 : | <code> public String getString(String prefId);</code><br> | ||
| 767 : | <code> public void setString(String prefId, String | ||
| 768 : | value);</code><br> | ||
| 769 : | <code> public void | ||
| 770 : | addListener(IPropertyChangeListener l);</code><br> | ||
| 771 : | <code> public void | ||
| 772 : | removeListener(IPropertyChangeListener | ||
| 773 : | l);</code><br> | ||
| 774 : | <code>}</code><br> | ||
| 775 : | <code></code><br> | ||
| 776 : | <code>/**</code><br> | ||
| 777 : | <code> * Concrete implementation of the IPreferences interface</code><br> | ||
| 778 : | <code> */</code><br> | ||
| 779 : | <code>public LocalPreferenceStore implements IPreferences, IDisposable {</code><br> | ||
| 780 : | <code> ListenerList listeners = new ListenerList();</code><br> | ||
| 781 : | <code> Preferences prefs;</code><br> | ||
| 782 : | <code> </code><br> | ||
| 783 : | <code> private class PropertyChangeListener | ||
| 784 : | implements | ||
| 785 : | Preferences.IPropertyChangeListener {</code><br> | ||
| 786 : | <code> /*</code><br> | ||
| 787 : | <code> * @see | ||
| 788 : | org.eclipse.core.runtime.Preferences.IPropertyChangeListener#propertyChange(org.eclipse.core.runtime.Preferences.PropertyChangeEvent)</code><br> | ||
| 789 : | <code> */</code><br> | ||
| 790 : | <code> public void | ||
| 791 : | propertyChange(Preferences.PropertyChangeEvent event) {</code><br> | ||
| 792 : | <code> | ||
| 793 : | firePropertyChangeEvent(event.getProperty(), event.getOldValue(), | ||
| 794 : | event.getNewValue());</code><br> | ||
| 795 : | <code> }</code><br> | ||
| 796 : | <code> }</code><br> | ||
| 797 : | <code></code><br> | ||
| 798 : | <code> PropertyChangeListener listener = new | ||
| 799 : | PropertyChangeListener();</code><br> | ||
| 800 : | <code></code><br> | ||
| 801 : | <code> /**</code><br> | ||
| 802 : | <code> * Create a wrapper around the given | ||
| 803 : | bundle's | ||
| 804 : | preference store.</code><br> | ||
| 805 : | <code> */</code><br> | ||
| 806 : | <code> public LocalPreferenceStore(Bundle | ||
| 807 : | pluginBundle) {</code><br> | ||
| 808 : | <code> Plugin plugin = | ||
| 809 : | Platform.getPlugin(pluginBundle.getSymbolicName());</code><br> | ||
| 810 : | <code> prefs = | ||
| 811 : | plugin.getPluginPreferences();</code><br> | ||
| 812 : | <code> prefs.addListener(listener);</code><br> | ||
| 813 : | <code> }</code><br> | ||
| 814 : | <code></code><br> | ||
| 815 : | <code> private void firePropertyChange(String name, | ||
| 816 : | Object | ||
| 817 : | oldValue, Object newValue) {</code><br> | ||
| 818 : | <code> PropertyChangeEvent event = | ||
| 819 : | new | ||
| 820 : | PropertyChangeEvent(this, name, oldValue, newValue);</code><br> | ||
| 821 : | <code> Object[] listeners = | ||
| 822 : | this.listeners.getListeners();</code><br> | ||
| 823 : | <code> for (int i= 0; i < | ||
| 824 : | listeners.length; i++)</code><br> | ||
| 825 : | <code> | ||
| 826 : | ((IPropertyChangeListener) listeners[i]).propertyChange(event);</code><br> | ||
| 827 : | <code> }</code><br> | ||
| 828 : | <code></code><br> | ||
| 829 : | <code> /**</code><br> | ||
| 830 : | <code> * This method will automatically be | ||
| 831 : | called | ||
| 832 : | when the component that uses this service is disposed.</code><br> | ||
| 833 : | <code> * It should clean up anything that has | ||
| 834 : | been | ||
| 835 : | allocated by the service.</code><br> | ||
| 836 : | <code> */</code><br> | ||
| 837 : | <code> public void dispose() {</code><br> | ||
| 838 : | <code> prefs.removeListener(listener);</code><br> | ||
| 839 : | <code> }</code><br> | ||
| 840 : | <code></code><br> | ||
| 841 : | <code> public String getString(String prefId) {</code><br> | ||
| 842 : | <code> return prefs.getString(prefId);</code><br> | ||
| 843 : | <code> }</code><br> | ||
| 844 : | <code></code><br> | ||
| 845 : | <code> public void setString(String prefId, String | ||
| 846 : | value) {</code><br> | ||
| 847 : | <code> prefs.setValue(prefId, value);</code><br> | ||
| 848 : | <code> }</code><br> | ||
| 849 : | <code>}</code><br> | ||
| 850 : | <code></code></div> | ||
| 851 : | <code></code><br> | ||
| 852 : | Similarly, components may implement the IDisposable interface. A | ||
| 853 : | component that implements IDisposable will be disposed before any of | ||
| 854 : | its services, as shown by the following example:<br> | ||
| 855 : | <br> | ||
| 856 : | <div style="margin-left: 40px;"><code>public LifecycleService | ||
| 857 : | implements IDisposable {</code><br> | ||
| 858 : | <code> public LifecycleService() {</code><br> | ||
| 859 : | <code> | ||
| 860 : | System.out.println("LifecycleService | ||
| 861 : | created");</code><br> | ||
| 862 : | <code> }</code><br> | ||
| 863 : | <code></code><br> | ||
| 864 : | <code> public void dispose() {</code><br> | ||
| 865 : | <code> | ||
| 866 : | System.out.println("LifecycleService | ||
| 867 : | destroyed");</code><br> | ||
| 868 : | <code> }</code><br> | ||
| 869 : | <code>}</code><br> | ||
| 870 : | <code></code><br> | ||
| 871 : | <code>public LifecycleView implements IDisposable {</code><br> | ||
| 872 : | <code> public LifecycleView(LifecycleService service) | ||
| 873 : | {</code><br> | ||
| 874 : | <code> System.out.println("LifecycleView | ||
| 875 : | created");</code><br> | ||
| 876 : | <code> }</code><br> | ||
| 877 : | <code></code><br> | ||
| 878 : | <code> public void dispose() {</code><br> | ||
| 879 : | <code> System.out.println("LifecycleView | ||
| 880 : | destroyed");</code><br> | ||
| 881 : | <code> }</code><br> | ||
| 882 : | <code>}</code><br> | ||
| 883 : | <code></code><br> | ||
| 884 : | </div> | ||
| 885 : | Finally, we could execute the following code to trace when the service | ||
| 886 : | is created and destroyed:<br> | ||
| 887 : | <div style="margin-left: 40px;"><br> | ||
| 888 : | IMutableContainerFactory factory = | ||
| 889 : | Components.getFactory().createDerivedFactory();<br> | ||
| 890 : | factory.addComponentImplementation(LifecycleService.class);<br> | ||
| 891 : | <br> | ||
| 892 : | System.out.println("Creating a LifecycleView");<br> | ||
| 893 : | <br> | ||
| 894 : | IContainer viewComponent = factory.createComponent(LifecycleView.class);<br> | ||
| 895 : | viewComponent.dispose();<br> | ||
| 896 : | <br> | ||
| 897 : | System.out.println("Creating two LifecycleViews");<br> | ||
| 898 : | <br> | ||
| 899 : | IContainer viewComponent2 = | ||
| 900 : | factory.createComponent(LifecycleView.class);<br> | ||
| 901 : | IContainer viewComponent3 = | ||
| 902 : | factory.createComponent(LifecycleView.class);<br> | ||
| 903 : | viewComponent3.dispose();<br> | ||
| 904 : | viewComponent2.dispose();<br> | ||
| 905 : | <br> | ||
| 906 : | </div> | ||
| 907 : | The code will generate the following output:<br> | ||
| 908 : | <br> | ||
| 909 : | <div style="margin-left: 40px;">Creating a LifecycleView<br> | ||
| 910 : | LifecycleService created<br> | ||
| 911 : | LifecycleView created<br> | ||
| 912 : | LifecycleView destroyed<br> | ||
| 913 : | LifecycleService destroyed<br> | ||
| 914 : | <br> | ||
| 915 : | Creating two LifecycleViews<br> | ||
| 916 : | LifecycleService created<br> | ||
| 917 : | LifecycleView created<br> | ||
| 918 : | LifecycleService created<br> | ||
| 919 : | LifecycleView created<br> | ||
| 920 : | LifecycleView destroyed<br> | ||
| 921 : | LifecycleService destroyed<br> | ||
| 922 : | LifecycleView destroyed<br> | ||
| 923 : | LifecycleService destroyed<br> | ||
| 924 : | <br> | ||
| 925 : | </div> | ||
| 926 : | <h2><a class="mozTocH2" name="mozTocId543223"></a>2.7 Dynamic Services</h2> | ||
| 927 : | Global service factories are dynamic in the sense that they come and go | ||
| 928 : | as plugins are installed or uninstalled. If a plugin providing a | ||
| 929 : | service is uninstalled, a new implementation is selected from a | ||
| 930 : | different plugin. If no other plugin provides that service, the service | ||
| 931 : | becomes unavailable and it will not be possible to construct components | ||
| 932 : | that use it.<br> | ||
| 933 : | <br> | ||
| 934 : | Service instances themselves are not dynamic. Once a service is | ||
| 935 : | created, it will exist for as long as the component it was created for. | ||
| 936 : | Even if the a plugin stops providing a service, all instances of that | ||
| 937 : | service will continue to exist until disposed. All services used by a | ||
| 938 : | component will be created before that component, so components do not | ||
| 939 : | need to query for the existence of services or to wait for services | ||
| 940 : | that will be created after-the-fact. <br> | ||
| 941 : | <br> | ||
| 942 : | Plugins that need more advanced dynamic support should use OSGI | ||
| 943 : | services. The component framework is not intended as a replacement for | ||
| 944 : | OSGI, and there is no point in reimplmenting things that OSGI already | ||
| 945 : | does well.<br> | ||
| 946 : | <h2><a class="mozTocH2" name="mozTocId886834"></a>2.8 Optional Services<br> | ||
| 947 : | </h2> | ||
| 948 : | Sometimes a component does not require a service but can make use of it | ||
| 949 : | service if it exists. This should usually be handled by explicitly | ||
| 950 : | creating an empty global implementation of the service, but sometimes | ||
| 951 : | it is more efficient to have the component explicitly check if the | ||
| 952 : | service exists. This can be done by taking an argument of type | ||
| 953 : | IAdaptable, like this:<br> | ||
| 954 : | <br> | ||
| 955 : | <div style="margin-left: 40px;"><code>/**<br> | ||
| 956 : | * A component with no dependencies, but which can optionally | ||
| 957 : | accept an IMemento for initialization.<br> | ||
| 958 : | */<br> | ||
| 959 : | public class MyComponent {<br> | ||
| 960 : | <br> | ||
| 961 : | String myName = "default name";<br> | ||
| 962 : | <br> | ||
| 963 : | public MyComponent(IAdaptable optionalServices) {<br> | ||
| 964 : | <br> | ||
| 965 : | </code><code> // Check if an IMemento | ||
| 966 : | service exists.</code><br> | ||
| 967 : | <code> IMemento memento = | ||
| 968 : | (IMemento)optionalServices.getAdapter(IMemento.class);<br> | ||
| 969 : | <br> | ||
| 970 : | if (memento != null) {<br> | ||
| 971 : | String name = | ||
| 972 : | memento.getString("name");<br> | ||
| 973 : | if (name != null) {<br> | ||
| 974 : | myName = name;<br> | ||
| 975 : | }<br> | ||
| 976 : | }<br> | ||
| 977 : | <br> | ||
| 978 : | System.out.println("created component | ||
| 979 : | with name = " + myName);<br> | ||
| 980 : | }<br> | ||
| 981 : | }<br> | ||
| 982 : | <br> | ||
| 983 : | </code></div> | ||
| 984 : | We could create the component like this:<br> | ||
| 985 : | <br> | ||
| 986 : | <div style="margin-left: 40px;"><code>// Create a memento with the name | ||
| 987 : | "custom name"<br> | ||
| 988 : | IMemento memento = XMLMemento.createWriteRoot("test");<br> | ||
| 989 : | memento.putString("name", "custom name");<br> | ||
| 990 : | <br> | ||
| 991 : | // Create a factory which knows about our memento<br> | ||
| 992 : | IMutableContainerFactory context = | ||
| 993 : | Components.getFactory().createDerivedFactory();<br> | ||
| 994 : | context.addComponentInstance(memento);<br> | ||
| 995 : | <br> | ||
| 996 : | // Use the factory to instantiate MyComponent<br> | ||
| 997 : | IContainer component = context.createContainer(MyComponent.class);<br> | ||
| 998 : | // Will print the message "created component with name = custom name"<br> | ||
| 999 : | component.dispose();</code><br> | ||
| 1000 : | <br> | ||
| 1001 : | </div> | ||
| 1002 : | We can also create the component without the optional IMemento:<br> | ||
| 1003 : | <br> | ||
| 1004 : | <div style="margin-left: 40px;"><code>IContainer component = | ||
| 1005 : | Components.getFactory().createContainer(MyComponent.class);<br> | ||
| 1006 : | // Will print the message "create component with name = default name"<br> | ||
| 1007 : | component.dispose();</code><code></code><br> | ||
| 1008 : | </div> | ||
| 1009 : | <br> | ||
| 1010 : | WARNING: This pattern should be used with care since it adds special | ||
| 1011 : | cases to the component code. It is mainly inteded for situations where | ||
| 1012 : | a component requires an open-ended set of services or where it is not | ||
| 1013 : | possible to offer a default implementation of a service.<br> | ||
| 1014 : | <br> | ||
| 1015 : | <h2><a class="mozTocH2" name="mozTocId163729"></a>2.9 Multiplexing | ||
| 1016 : | services</h2> | ||
| 1017 : | In UI code, it is common to create aggregate components that have an | ||
| 1018 : | "active" child. Many of the adapters that the aggregate offers its | ||
| 1019 : | parent will redirect their implementation to the active child. We refer | ||
| 1020 : | to this as "multiplexing" the service. The aggregate itself will know | ||
| 1021 : | how to select its active child, but it may not know about all the | ||
| 1022 : | services that need to be multiplexed. The default implementation of a | ||
| 1023 : | service determines how and if it should be multiplexed.<br> | ||
| 1024 : | <br> | ||
| 1025 : | This can be explained better using a concrete example. Workbench parts | ||
| 1026 : | offer an IFocusable adapter to their parent that allows their parent to | ||
| 1027 : | give them focus. <br> | ||
| 1028 : | <br> | ||
| 1029 : | <code></code> | ||
| 1030 : | <div style="margin-left: 40px;"><code>/**<br> | ||
| 1031 : | * Parts can implement this interface if they wish to overload the | ||
| 1032 : | default<br> | ||
| 1033 : | * setFocus behavior.<br> | ||
| 1034 : | */<br> | ||
| 1035 : | public interface IFocusable {<br> | ||
| 1036 : | /**<br> | ||
| 1037 : | * Gives focus to the part<br> | ||
| 1038 : | */<br> | ||
| 1039 : | public void setFocus();<br> | ||
| 1040 : | }</code><br> | ||
| 1041 : | <br> | ||
| 1042 : | </div> | ||
| 1043 : | This interface should be multiplexed by default. For example, if we | ||
| 1044 : | call setFocus() on a multi-page editor that doesn't explicitly | ||
| 1045 : | implement the IFocusable interface, it should redirect focus to its | ||
| 1046 : | active child. If the part doesn't have an active child, focus should go | ||
| 1047 : | directly to the part's main control. To supply this default behavior, | ||
| 1048 : | we provide an implementation of IFocusable as a global service:<br> | ||
| 1049 : | <br> | ||
| 1050 : | <code></code> | ||
| 1051 : | <div style="margin-left: 40px;"><code>/**<br> | ||
| 1052 : | * Default implementation of the IFocusable service. If a part | ||
| 1053 : | doesn't explicitly<br> | ||
| 1054 : | * provide an adapter for IFocusable, this implementation will be | ||
| 1055 : | used.<br> | ||
| 1056 : | */<br> | ||
| 1057 : | public class DefaultFocusable implements IFocusable {<br> | ||
| 1058 : | <br> | ||
| 1059 : | private Composite control;<br> | ||
| 1060 : | private IMultiplexer activePart;<br> | ||
| 1061 : | <br> | ||
| 1062 : | /**<br> | ||
| 1063 : | * Creates the default implementation of | ||
| 1064 : | IFocusable, given the main control<br> | ||
| 1065 : | * of the part and an IMultiplexer that can be | ||
| 1066 : | queried for the active child.<br> | ||
| 1067 : | * <br> | ||
| 1068 : | * @param toGiveFocus main control of the pane<br> | ||
| 1069 : | * @param activePartProvider multiplexer that | ||
| 1070 : | returns the active part<br> | ||
| 1071 : | */<br> | ||
| 1072 : | public DefaultFocusable(Composite toGiveFocus, | ||
| 1073 : | IMultiplexer activePartProvider) {<br> | ||
| 1074 : | control = toGiveFocus;<br> | ||
| 1075 : | activePart = activePartProvider;<br> | ||
| 1076 : | }<br> | ||
| 1077 : | <br> | ||
| 1078 : | /**<br> | ||
| 1079 : | * First, try to give focus to the active | ||
| 1080 : | child. If there is no active child, give focus to<br> | ||
| 1081 : | * the main control.<br> | ||
| 1082 : | */<br> | ||
| 1083 : | public void setFocus() {<br> | ||
| 1084 : | // If this part has the notion of | ||
| 1085 : | an active child, give that child focus<br> | ||
| 1086 : | Object current = | ||
| 1087 : | activePart.getCurrent();<br> | ||
| 1088 : | if (current != null) {<br> | ||
| 1089 : | IFocusable | ||
| 1090 : | focusable = (IFocusable)Components.getAdapter(current, | ||
| 1091 : | IFocusable.class);<br> | ||
| 1092 : | if (focusable | ||
| 1093 : | != null) {<br> | ||
| 1094 : | | ||
| 1095 : | focusable.setFocus();<br> | ||
| 1096 : | | ||
| 1097 : | return;<br> | ||
| 1098 : | }<br> | ||
| 1099 : | }<br> | ||
| 1100 : | <br> | ||
| 1101 : | // If the part has no children, | ||
| 1102 : | then give focus to the part itself. | ||
| 1103 : | <br> | ||
| 1104 : | control.setFocus();<br> | ||
| 1105 : | }<br> | ||
| 1106 : | }</code><br> | ||
| 1107 : | <br> | ||
| 1108 : | </div> | ||
| 1109 : | Here is the XML markup to register the service:<br> | ||
| 1110 : | <br> | ||
| 1111 : | <div style="margin-left: 40px;"><code><service<br> | ||
| 1112 : | | ||
| 1113 : | interface="org.eclipse.ui.workbench.services.IFocusable"<br> | ||
| 1114 : | | ||
| 1115 : | class="org.eclipse.ui.internal.part.serviceimplementation.DefaultFocusable"/></code><code></code><br> | ||
| 1116 : | </div> | ||
| 1117 : | <br> | ||
| 1118 : | All of the services we have looked at so far have been offered by a | ||
| 1119 : | parent for use in a child's constructor. In this case, the service is | ||
| 1120 : | offered as an adapter on the child to be used by the parent. In both | ||
| 1121 : | cases, the service is declared in the same way. Any service that | ||
| 1122 : | supports multiplexing takes an IMultiplexer in its constructor. If the | ||
| 1123 : | part does not support multiplexing, the IMultiplexer will always | ||
| 1124 : | returns null from getCurrent. Here is an example part that supports | ||
| 1125 : | multiplexing:<br> | ||
| 1126 : | <code><br> | ||
| 1127 : | </code> | ||
| 1128 : | <div style="margin-left: 40px;"><code>/**<br> | ||
| 1129 : | * Part that explicitly implements IFocusable.<br> | ||
| 1130 : | */<br> | ||
| 1131 : | public class Page implements IFocusable {<br> | ||
| 1132 : | Text textField;<br> | ||
| 1133 : | <br> | ||
| 1134 : | public Page(Composite control) {<br> | ||
| 1135 : | textField = new Text(control, SWT.NONE);<br> | ||
| 1136 : | }<br> | ||
| 1137 : | <br> | ||
| 1138 : | public void setFocus() {<br> | ||
| 1139 : | textField.setFocus();<br> | ||
| 1140 : | }<br> | ||
| 1141 : | }<br> | ||
| 1142 : | <br> | ||
| 1143 : | /**<br> | ||
| 1144 : | * Non-multiplexing part that relies on the DefaultFocusable | ||
| 1145 : | service<br> | ||
| 1146 : | * to give focus to its composite.<br> | ||
| 1147 : | */<br> | ||
| 1148 : | public class Page2 {<br> | ||
| 1149 : | Text textField;<br> | ||
| 1150 : | <br> | ||
| 1151 : | public Page(Composite control) {<br> | ||
| 1152 : | textField = new Text(control, SWT.NONE);<br> | ||
| 1153 : | }<br> | ||
| 1154 : | }<br> | ||
| 1155 : | <br> | ||
| 1156 : | /**<br> | ||
| 1157 : | * Multiplexing part that contains a Page, a Page2, and a checkbox.<br> | ||
| 1158 : | * When the checkbox is selected, the first page will be active. | ||
| 1159 : | When the<br> | ||
| 1160 : | * checkbox is deselected, the second page will be active. Calling | ||
| 1161 : | setFocus<br> | ||
| 1162 : | * on the MultiplexingView will always give focus to the active | ||
| 1163 : | page.<br> | ||
| 1164 : | */<br> | ||
| 1165 : | public class MultiplexingView implements IDisposable, IAdaptable {<br> | ||
| 1166 : | private IContainer page1;<br> | ||
| 1167 : | private IContainer page2;<br> | ||
| 1168 : | <br> | ||
| 1169 : | // Helper class that implements the IMultiplexer | ||
| 1170 : | interface<br> | ||
| 1171 : | private Multiplexer multiplexer = new Multiplexer();<br> | ||
| 1172 : | private Button checkBox;<br> | ||
| 1173 : | <br> | ||
| 1174 : | public MultiplexingView(Composite myControl) {<br> | ||
| 1175 : | myControl.setLayout(new RowLayout());<br> | ||
| 1176 : | <br> | ||
| 1177 : | checkBox = new Button(myControl, | ||
| 1178 : | SWT.CHECK);<br> | ||
| 1179 : | checkBox.addSelectionListener(new | ||
| 1180 : | SelectionAdapter() {<br> | ||
| 1181 : | public void | ||
| 1182 : | widgetSelected(SelectionEvent e) {<br> | ||
| 1183 : | | ||
| 1184 : | updateActivePart();<br> | ||
| 1185 : | }<br> | ||
| 1186 : | });<br> | ||
| 1187 : | <br> | ||
| 1188 : | checkBox.setText("Activate part 1");<br> | ||
| 1189 : | <br> | ||
| 1190 : | // Create the child controls<br> | ||
| 1191 : | IMutableComponentFactory childFactory = | ||
| 1192 : | Components.getFactory().createDerivedFactory();<br> | ||
| 1193 : | <br> | ||
| 1194 : | // Use the CompositeAdapter class we | ||
| 1195 : | created earlier in order to provide each page<br> | ||
| 1196 : | // with its own control.<br> | ||
| 1197 : | childFactory.addComponentInstance(new | ||
| 1198 : | CompositeAdapter(myControl));<br> | ||
| 1199 : | <br> | ||
| 1200 : | page1 = | ||
| 1201 : | childFactory.createContainer(Page.class);<br> | ||
| 1202 : | page2 = | ||
| 1203 : | childFactory.createContainer(Page2.class);<br> | ||
| 1204 : | <br> | ||
| 1205 : | // Activate the initial part<br> | ||
| 1206 : | </code><code> updateActivePart();</code><br> | ||
| 1207 : | <code> }<br> | ||
| 1208 : | <br> | ||
| 1209 : | private final void updateActivePart() {<br> | ||
| 1210 : | </code><code> if | ||
| 1211 : | (checkBox.getSelection()) {<br> | ||
| 1212 : | | ||
| 1213 : | multiplexer.setCurrent(page1);<br> | ||
| 1214 : | } else {<br> | ||
| 1215 : | | ||
| 1216 : | multiplexer.setCurrent(page2);<br> | ||
| 1217 : | }</code><br> | ||
| 1218 : | <code> }<br> | ||
| 1219 : | <br> | ||
| 1220 : | /**<br> | ||
| 1221 : | * In order to indicate that this is a | ||
| 1222 : | multiplexing part, we need to implement<br> | ||
| 1223 : | * the IMultiplexer adapter. We simply redirect | ||
| 1224 : | to the multiplexer object.<br> | ||
| 1225 : | */<br> | ||
| 1226 : | public Object getAdapter(Class adapterType) {<br> | ||
| 1227 : | if (adapterType == IMultiplexer.class) {<br> | ||
| 1228 : | return multiplexer;<br> | ||
| 1229 : | }<br> | ||
| 1230 : | }<br> | ||
| 1231 : | <br> | ||
| 1232 : | /**<br> | ||
| 1233 : | * Release any resources allocated in the | ||
| 1234 : | constructor<br> | ||
| 1235 : | */<br> | ||
| 1236 : | public void dispose() {<br> | ||
| 1237 : | page1.dispose();<br> | ||
| 1238 : | page2.dispose();<br> | ||
| 1239 : | }<br> | ||
| 1240 : | }</code><br> | ||
| 1241 : | </div> | ||
| 1242 : | <br> | ||
| 1243 : | Notice that MultiplexingView itself doesn't know about the IFocusable | ||
| 1244 : | interface, however if we were to create a MultiplexingView and ask its | ||
| 1245 : | container for an IFocusable interface, we would get an interface that | ||
| 1246 : | behaves as expected. <br> | ||
| 1247 : | <br> | ||
| 1248 : | <div style="margin-left: 40px;"><code></code><code>// Stupid example | ||
| 1249 : | that creates a MultiplexingView, gives it focus, then destroys it<br> | ||
| 1250 : | <br> | ||
| 1251 : | // Create a MultiplexingView</code><br> | ||
| 1252 : | <code>IMutableComponentFactory childFactory = | ||
| 1253 : | Components.getFactory().createDerivedFactory();<br> | ||
| 1254 : | </code><code>childFactory.addComponentInstance(new | ||
| 1255 : | CompositeAdapter(myControl));<br> | ||
| 1256 : | <br> | ||
| 1257 : | IContainer viewContainer = | ||
| 1258 : | childFactory.createContainer(MultiplexingView.class);<br> | ||
| 1259 : | <br> | ||
| 1260 : | // Give focus to the active part<br> | ||
| 1261 : | IFocusable focusable = viewContainer.getAdapter(IFocusable.class);<br> | ||
| 1262 : | focusable.setFocus();<br> | ||
| 1263 : | </code><code></code><code><br> | ||
| 1264 : | // Destroy the view<br> | ||
| 1265 : | viewContainer.dispose();<br> | ||
| 1266 : | </code><code></code></div> | ||
| 1267 : | <code> <br> | ||
| 1268 : | </code>This example also shows how the same service can work for both | ||
| 1269 : | multiplexing and non-multiplexing parts. The Page2 class doesn't | ||
| 1270 : | implement IFocusable or IMultiplexing part, but we are still able to | ||
| 1271 : | ask for an IFocusable interface and use it to give focus to the part.<br> | ||
| 1272 : | <h1><a class="mozTocH1" name="mozTocId707022"></a>3.0 Views and Editors | ||
| 1273 : | as Components</h1> | ||
| 1274 : | TODO<br> | ||
| 1275 : | <br> | ||
| 1276 : | <br> | ||
| 1277 : | </body> | ||
| 1278 : | </html> |
| help@eclipse.org | ViewVC Help |
| Powered by ViewVC 1.0.3 |
