platform-ui-home/components-proposal/ComponentFrameworkProposal.html
Parent Directory
|
Revision Log
Revision 1.2 - (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 : | sxenos | 1.2 | <div style="text-align: center;">Draft<br> |
| 16 : | Revision 1.0.4<br> | ||
| 17 : | sxenos | 1.1 | By Stefan Xenos and Nick Edgar<br> |
| 18 : | sxenos | 1.2 | Last modified 2004/11/05<br> |
| 19 : | sxenos | 1.1 | </div> |
| 20 : | <br> | ||
| 21 : | <br> | ||
| 22 : | <br> | ||
| 23 : | <font size="+3"><span style="font-weight: bold;">Table of Contents</span></font><br> | ||
| 24 : | <ul id="mozToc"> | ||
| 25 : | <!--mozToc h1 1 h2 2 h3 3 h4 4 h5 5 h6 6--><li><a href="#mozTocId511305">1.0 | ||
| 26 : | Introduction | ||
| 27 : | </a> | ||
| 28 : | <ul> | ||
| 29 : | sxenos | 1.2 | <li><a href="#mozTocId619855">1.1 Understanding the problem</a></li> |
| 30 : | <li><a href="#mozTocId340323">1.2 Requirements | ||
| 31 : | </a> | ||
| 32 : | <ul> | ||
| 33 : | <li><a href="#mozTocId29093">1.2.1 Robustness / | ||
| 34 : | leak | ||
| 35 : | proofing | ||
| 36 : | </a></li> | ||
| 37 : | <li><a href="#mozTocId223982">1.2.2 Nesting of | ||
| 38 : | components</a></li> | ||
| 39 : | <li><a href="#mozTocId634024">1.2.3 Creating parts | ||
| 40 : | outside the workbench</a></li> | ||
| 41 : | <li><a href="#mozTocId800379">1.2.4 Scalability</a></li> | ||
| 42 : | <li><a href="#mozTocId866479">1.2.5 Ease of use | ||
| 43 : | </a></li> | ||
| 44 : | </ul> | ||
| 45 : | </li> | ||
| 46 : | sxenos | 1.1 | </ul> |
| 47 : | </li> | ||
| 48 : | sxenos | 1.2 | <li><a href="#mozTocId416167">2.0 Component |
| 49 : | Framework | ||
| 50 : | sxenos | 1.1 | </a> |
| 51 : | <ul> | ||
| 52 : | sxenos | 1.2 | <li><a href="#mozTocId489102">2.1 Instantiating a |
| 53 : | view | ||
| 54 : | sxenos | 1.1 | </a></li> |
| 55 : | sxenos | 1.2 | <li><a href="#mozTocId717633">2.2 Instantiating |
| 56 : | components in general | ||
| 57 : | sxenos | 1.1 | </a></li> |
| 58 : | sxenos | 1.2 | <li><a href="#mozTocId450231">2.3 Creating |
| 59 : | dependent components on demand </a></li> | ||
| 60 : | <li><a href="#mozTocId928619">2.4 Declaring Component Interfaces</a></li> | ||
| 61 : | <li><a href="#mozTocId749133">2.5 Using Component Interfaces</a></li> | ||
| 62 : | <li><a href="#mozTocId705695">2.6 Lifecycle</a></li> | ||
| 63 : | <li><a href="#mozTocId543223">2.7 Dynamic Interfaces</a></li> | ||
| 64 : | <li><a href="#mozTocId886834">2.8 Optional Interface | ||
| 65 : | sxenos | 1.1 | </a></li> |
| 66 : | sxenos | 1.2 | <li><a href="#mozTocId578639">2.8 Component Scopes</a></li> |
| 67 : | <li><a href="#mozTocId762751">2.9 Shared Adapters</a></li> | ||
| 68 : | </ul> | ||
| 69 : | </li> | ||
| 70 : | <li><a href="#mozTocId707022">3.0 Views and Editors | ||
| 71 : | as Components</a> | ||
| 72 : | <ul> | ||
| 73 : | <li><a href="#mozTocId125361">3.1 Interfaces offered by the | ||
| 74 : | workbench</a> | ||
| 75 : | <ul> | ||
| 76 : | <li><a href="#mozTocId163729">3.1.1 IMultiPart: Redirecting | ||
| 77 : | adapters from the active child | ||
| 78 : | </a></li> | ||
| 79 : | <li><a href="#mozTocId483376">3.1.2 INameable: Changing the | ||
| 80 : | name of a part</a></li> | ||
| 81 : | <li><a href="#mozTocId969580">3.1.3 IPartFactory: Creating | ||
| 82 : | child parts</a></li> | ||
| 83 : | </ul> | ||
| 84 : | </li> | ||
| 85 : | sxenos | 1.1 | </ul> |
| 86 : | </li> | ||
| 87 : | </ul> | ||
| 88 : | <br> | ||
| 89 : | <h1><a class="mozTocH1" name="mozTocId511305"></a>1.0 Introduction<br> | ||
| 90 : | </h1> | ||
| 91 : | This proposal outlines a framework for managing complex executable | ||
| 92 : | extensions. This framework is a first step toward allowing views and | ||
| 93 : | editors to be combined recursively, and created inside arbitrary SWT | ||
| 94 : | composites. Although it is intended for creating reusable UI | ||
| 95 : | components, the framework is useful for any extension point that | ||
| 96 : | creates complicated Java objects. For this reason, we will refer to the | ||
| 97 : | objects being created as "components" even though our components are | ||
| 98 : | typically views and editors.<br> | ||
| 99 : | <br> | ||
| 100 : | This proposal is broken into three parts. The introduction describes | ||
| 101 : | the motivation for creating the component framework and its | ||
| 102 : | requirements. The second section describes the component framework in | ||
| 103 : | itself, which could be used for any executable extension point. The | ||
| 104 : | third section describes how the workbench will use the component | ||
| 105 : | framework for views and editors.<br> | ||
| 106 : | <br> | ||
| 107 : | sxenos | 1.2 | <h2><a class="mozTocH2" name="mozTocId619855"></a>1.1 Understanding the |
| 108 : | problem</h2> | ||
| 109 : | This section explains why the component framework is needed to support | ||
| 110 : | nested parts. We will show the problems involved in designing an | ||
| 111 : | extensible Site interface, and will show how they can be solved using | ||
| 112 : | the patterns in the component framework. Skip to the next sections if | ||
| 113 : | you don't need convincing and just want to see the specification. | ||
| 114 : | IMPORTANT NOTE: | ||
| 115 : | the code examples in this section are only intended to illustrate | ||
| 116 : | patterns and do not reflect the actual implementation<br> | ||
| 117 : | <br> | ||
| 118 : | Currently, parts communicate with their parent through their site. The | ||
| 119 : | site provides the view with access to the outside world. In general, it | ||
| 120 : | is better to provide new API on the part's site than have the part | ||
| 121 : | reach to some global object, since the site can provide some context | ||
| 122 : | and can manage resources allocated to the part.<br> | ||
| 123 : | <br> | ||
| 124 : | Here is a simplified version of the View and Site interface. We've | ||
| 125 : | omitted most of the details. The important part is that the View is | ||
| 126 : | given a Site when it is initialized, it uses various methods on the | ||
| 127 : | Site throughout its life, and the Site has a dispose() method that can | ||
| 128 : | clean up after the View.<br> | ||
| 129 : | <code><br> | ||
| 130 : | </code> | ||
| 131 : | <div style="margin-left: 40px;"><code>/**<br> | ||
| 132 : | * A view's main interface to the world.<br> | ||
| 133 : | */<br> | ||
| 134 : | public class Site {<br> | ||
| 135 : | /**<br> | ||
| 136 : | * Create a view site, given the plugin that | ||
| 137 : | contains the implementation for the view<br> | ||
| 138 : | */<br> | ||
| 139 : | public Site(Plugin viewPlugin) {<br> | ||
| 140 : | }<br> | ||
| 141 : | <br> | ||
| 142 : | /**<br> | ||
| 143 : | * Return the view's ID<br> | ||
| 144 : | */<br> | ||
| 145 : | public String getId() {...};<br> | ||
| 146 : | <br> | ||
| 147 : | public IActionBars getActionBars() {...};<br> | ||
| 148 : | <br> | ||
| 149 : | /**<br> | ||
| 150 : | * Clean up any leftover resources allocated by | ||
| 151 : | the site<br> | ||
| 152 : | */<br> | ||
| 153 : | public void dispose() {<br> | ||
| 154 : | };<br> | ||
| 155 : | }<br> | ||
| 156 : | <br> | ||
| 157 : | /**<br> | ||
| 158 : | * Base class for all views.<br> | ||
| 159 : | */<br> | ||
| 160 : | public abstract class View {<br> | ||
| 161 : | private Site mySite;<br> | ||
| 162 : | <br> | ||
| 163 : | public View(Site viewSite) {<br> | ||
| 164 : | mySite = viewSite;<br> | ||
| 165 : | }<br> | ||
| 166 : | <br> | ||
| 167 : | public Site getSite() {<br> | ||
| 168 : | return mySite;<br> | ||
| 169 : | }<br> | ||
| 170 : | <br> | ||
| 171 : | public abstract String getTitle();<br> | ||
| 172 : | <br> | ||
| 173 : | public void dispose() {};<br> | ||
| 174 : | }<br> | ||
| 175 : | <br> | ||
| 176 : | /**<br> | ||
| 177 : | * A factory that can create views by ID.<br> | ||
| 178 : | */<br> | ||
| 179 : | public ViewFactory {<br> | ||
| 180 : | public static View createView(String | ||
| 181 : | viewExtensionId, Site viewSite) {<br> | ||
| 182 : | //... create a view and return it<br> | ||
| 183 : | }<br> | ||
| 184 : | }<br> | ||
| 185 : | </code></div> | ||
| 186 : | <br> | ||
| 187 : | Anyone that wants to instantiate a view would need to create an | ||
| 188 : | instance of Site and create the view by calling ViewFactory.createView. | ||
| 189 : | It would be possible to subclass Site in order to provide the part with | ||
| 190 : | additional context. For example, a subclass of Site could overload | ||
| 191 : | getActionBars() to substitute a different implementation of IActionBars | ||
| 192 : | to be used by the view. <br> | ||
| 193 : | <br> | ||
| 194 : | One of the problems with using the Site as the main interface to a view | ||
| 195 : | is that we continually need to add new methods to the Site base class. | ||
| 196 : | For example, we might decide to add a logError method that allows views | ||
| 197 : | to log exceptions to their plugin log without needing to reach to their | ||
| 198 : | Plugin object or worry about constructing IStatus instances with the | ||
| 199 : | correct plugin ID.<br> | ||
| 200 : | <br> | ||
| 201 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 202 : | <code> * A view's main interface to the world.</code><br> | ||
| 203 : | <code> */</code><br> | ||
| 204 : | <code>public class Site {<br> | ||
| 205 : | Plugin plugin;<br> | ||
| 206 : | <br> | ||
| 207 : | </code><code></code><code> public Site(Plugin | ||
| 208 : | viewPlugin) {<br> | ||
| 209 : | plugin = viewPlugin;<br> | ||
| 210 : | }<br> | ||
| 211 : | <br> | ||
| 212 : | // ...all other Site methods omitted...<br> | ||
| 213 : | <br> | ||
| 214 : | </code><code> public void logError(Throwable t) {<br> | ||
| 215 : | plugin.getLog().log(new Status(..., t));<br> | ||
| 216 : | };<br> | ||
| 217 : | </code><code>}</code><br> | ||
| 218 : | </div> | ||
| 219 : | <br> | ||
| 220 : | The site could also provide the view with localized ways to allocate | ||
| 221 : | resources. For example, if the view could allocate Images through its | ||
| 222 : | site, the Site could guarantee that the images would be cleaned up | ||
| 223 : | properly when the view is destroyed.<br> | ||
| 224 : | <div style="margin-left: 40px;"><code><br> | ||
| 225 : | /**</code><br> | ||
| 226 : | <code> | ||
| 227 : | * A view's main interface to the world.</code><br> | ||
| 228 : | <code> | ||
| 229 : | */</code><br> | ||
| 230 : | <code> | ||
| 231 : | public class Site {<br> | ||
| 232 : | <br> | ||
| 233 : | // .... everything in the above version of Site plus | ||
| 234 : | this:<br> | ||
| 235 : | <br> | ||
| 236 : | Map allocatedImages = new HashMap();<br> | ||
| 237 : | <br> | ||
| 238 : | public Image createImage(ImageDescriptor toCreate) {<br> | ||
| 239 : | Image image = | ||
| 240 : | toCreate.createImage(true);<br> | ||
| 241 : | allocatedImages.put(toCreate, image);<br> | ||
| 242 : | return image;<br> | ||
| 243 : | }<br> | ||
| 244 : | <br> | ||
| 245 : | public void destroyImage(ImageDescriptor toDestroy) {<br> | ||
| 246 : | Image image = | ||
| 247 : | allocatedImages.get(toDestroy);<br> | ||
| 248 : | if (image != null) {<br> | ||
| 249 : | | ||
| 250 : | allocatedImages.remove(toDestroy);<br> | ||
| 251 : | image.dispose();<br> | ||
| 252 : | }<br> | ||
| 253 : | }<br> | ||
| 254 : | <br> | ||
| 255 : | public void dispose() {<br> | ||
| 256 : | Collection values = | ||
| 257 : | allocatedImages.getValueSet();<br> | ||
| 258 : | Iterator iter = values.iterator();<br> | ||
| 259 : | while (iter.hasNext()) {<br> | ||
| 260 : | Image img = | ||
| 261 : | (Image)iter.next();<br> | ||
| 262 : | img.dispose();<br> | ||
| 263 : | }<br> | ||
| 264 : | }</code><code><br> | ||
| 265 : | </code><code> | ||
| 266 : | }<br> | ||
| 267 : | <br> | ||
| 268 : | </code></div> | ||
| 269 : | In order to provide the best protection against leaks, new API should | ||
| 270 : | use this pattern as much as possible for views. The obvious problems | ||
| 271 : | with this are:<br> | ||
| 272 : | <ol> | ||
| 273 : | <li>Whoever implements the Site class can't possibly know about every | ||
| 274 : | type of resource that might be allocated by every view</li> | ||
| 275 : | <li>The Site interface would become ridiculously large and | ||
| 276 : | complicated.<br> | ||
| 277 : | </li> | ||
| 278 : | </ol> | ||
| 279 : | It would be possible to work around this by writing new subclasses of | ||
| 280 : | Site that add new methods, but then Views that use the new APIs would | ||
| 281 : | not function properly when given a different Site implementation. The | ||
| 282 : | ideal solution would allow plugins to contribute new default behavior | ||
| 283 : | to the Site base class, and only require the Site class to be | ||
| 284 : | subclassed when this behavior needs to be specialized. This can be | ||
| 285 : | achieved by making Site adaptable. Rather than always adding new | ||
| 286 : | methods to Site, plugins couldcontribute adapters. Subclasses of site | ||
| 287 : | would still be able to override the default adapter behavior by | ||
| 288 : | explicitly implementing the adapter interface, but parts that use the | ||
| 289 : | adapter would no longer be tied to a particular Site implementation.<br> | ||
| 290 : | <br> | ||
| 291 : | Here is an example that demonstrates the pattern:<br> | ||
| 292 : | <br> | ||
| 293 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 294 : | <code> | ||
| 295 : | * Adapter for a view site that allows allocation/deallocation of | ||
| 296 : | images</code><br> | ||
| 297 : | <code> | ||
| 298 : | */</code><br> | ||
| 299 : | <code> | ||
| 300 : | public interface IImageAllocator {<br> | ||
| 301 : | public Image createImage(ImageDescriptor toCreate);<br> | ||
| 302 : | public void destroyImage(ImageDescriptor toDestroy);<br> | ||
| 303 : | </code><code>}<br> | ||
| 304 : | <br> | ||
| 305 : | /**<br> | ||
| 306 : | * Implementation of the image allocator adapter provided via XML<br> | ||
| 307 : | * by some plugin.<br> | ||
| 308 : | */<br> | ||
| 309 : | public class ImageAllocator implements IImageAllocator, IDisposable {<br> | ||
| 310 : | </code><code>public Image | ||
| 311 : | createImage(ImageDescriptor toCreate) {<br> | ||
| 312 : | ...<br> | ||
| 313 : | };<br> | ||
| 314 : | <br> | ||
| 315 : | public void destroyImage(ImageDescriptor toDestroy) {<br> | ||
| 316 : | ...<br> | ||
| 317 : | };<br> | ||
| 318 : | <br> | ||
| 319 : | public void dispose() {<br> | ||
| 320 : | // Dispose any images leaked through | ||
| 321 : | createImage<br> | ||
| 322 : | };<br> | ||
| 323 : | </code><code>}<br> | ||
| 324 : | <br> | ||
| 325 : | /**<br> | ||
| 326 : | * Next attempt at a site interface<br> | ||
| 327 : | */<br> | ||
| 328 : | public class Site implements IAdaptable {<br> | ||
| 329 : | <br> | ||
| 330 : | List disposableAdapters = new ArrayList();<br> | ||
| 331 : | <br> | ||
| 332 : | public Object getAdapter(Class adapterType) {<br> | ||
| 333 : | </code><code> Object result = //</code><code>...some | ||
| 334 : | magic that looks through the adapter extension point and<br> | ||
| 335 : | | ||
| 336 : | //creates an instance of ImageAllocator<br> | ||
| 337 : | <br> | ||
| 338 : | if (adapter instanceof IDisposable) {<br> | ||
| 339 : | | ||
| 340 : | disposableAdapters.add(adapter);<br> | ||
| 341 : | }<br> | ||
| 342 : | <br> | ||
| 343 : | return result;<br> | ||
| 344 : | }<br> | ||
| 345 : | <br> | ||
| 346 : | /**<br> | ||
| 347 : | * Allow all the adapters to clean up anything | ||
| 348 : | they allocated.<br> | ||
| 349 : | */<br> | ||
| 350 : | public void dispose() {<br> | ||
| 351 : | Iterator iter = | ||
| 352 : | disposableAdapters.iterator();<br> | ||
| 353 : | while(iter.hasNext()) {<br> | ||
| 354 : | IDisposable next = | ||
| 355 : | (IDisposable)iter.next();<br> | ||
| 356 : | <br> | ||
| 357 : | next.dispose();<br> | ||
| 358 : | }<br> | ||
| 359 : | }<br> | ||
| 360 : | }<br> | ||
| 361 : | <br> | ||
| 362 : | </code><code>/**<br> | ||
| 363 : | * Base class for all views.<br> | ||
| 364 : | */<br> | ||
| 365 : | public abstract class View {<br> | ||
| 366 : | private IAdaptable mySite;<br> | ||
| 367 : | <br> | ||
| 368 : | public View(IAdaptable viewSite) {<br> | ||
| 369 : | mySite = viewSite;<br> | ||
| 370 : | }<br> | ||
| 371 : | <br> | ||
| 372 : | public IAdaptable getSite() {<br> | ||
| 373 : | return mySite;<br> | ||
| 374 : | }<br> | ||
| 375 : | <br> | ||
| 376 : | public abstract String getTitle();<br> | ||
| 377 : | <br> | ||
| 378 : | public void dispose() {};<br> | ||
| 379 : | }</code><br> | ||
| 380 : | </div> | ||
| 381 : | <br> | ||
| 382 : | Notice that this type of adapter is slightly different from most other | ||
| 383 : | adapters in Eclipse since it keeps some state and need to be notified | ||
| 384 : | when the Site is disposed. <br> | ||
| 385 : | <br> | ||
| 386 : | A particular view would look like this:<br> | ||
| 387 : | <br> | ||
| 388 : | <div style="margin-left: 40px;"><code>/**<br> | ||
| 389 : | * My implementation of a view<br> | ||
| 390 : | */<br> | ||
| 391 : | public class MyView extends View {<br> | ||
| 392 : | IImageAllocator allocator;<br> | ||
| 393 : | <br> | ||
| 394 : | public MyView(IAdaptable viewSite) {<br> | ||
| 395 : | allocator = | ||
| 396 : | (IImageAllocator)viewSite.getAdapter(IImageAllocator.class);<br> | ||
| 397 : | <br> | ||
| 398 : | if (allocator == null) {<br> | ||
| 399 : | throw new Exception("I | ||
| 400 : | can't exist without images!");<br> | ||
| 401 : | }<br> | ||
| 402 : | }<br> | ||
| 403 : | <br> | ||
| 404 : | //...<br> | ||
| 405 : | }<br> | ||
| 406 : | <br> | ||
| 407 : | </code></div> | ||
| 408 : | Most views will contain similar code in their constructor that requests | ||
| 409 : | a set of adapters from their site and check that they aren't null. We | ||
| 410 : | can add some syntactic sugar to remove this repetition. Rather than | ||
| 411 : | having the view explicitly request every adapter it needs, it can take | ||
| 412 : | them as arguments to its constructor and the factory that creates the | ||
| 413 : | view can use reflection to ensure that it gets all the adapters it | ||
| 414 : | needs. Using this pattern, the same view would look like this:<br> | ||
| 415 : | <br> | ||
| 416 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 417 : | <code> * My implementation of a view</code><br> | ||
| 418 : | <code> */</code><br> | ||
| 419 : | <code>public class MyView extends View {</code><br> | ||
| 420 : | <code> IImageAllocator allocator;</code><br> | ||
| 421 : | <code></code><br> | ||
| 422 : | <code> public MyView(IImageAllocator imageAllocator) {<br> | ||
| 423 : | allocator = imageAllocator;<br> | ||
| 424 : | </code><code></code><code> }</code><br> | ||
| 425 : | <code></code><br> | ||
| 426 : | <code> //...</code><br> | ||
| 427 : | <code>}<br> | ||
| 428 : | <br> | ||
| 429 : | </code></div> | ||
| 430 : | Since the entire Site API would be provided by adapters, the Site class | ||
| 431 : | could be reduced to this:<br> | ||
| 432 : | <br> | ||
| 433 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 434 : | <code> * My implementation of a view</code><br> | ||
| 435 : | <code> */</code><br> | ||
| 436 : | <code>public abstract class Site implements IAdaptable {<br> | ||
| 437 : | public abstract Object getAdapter(Class adapter);<br> | ||
| 438 : | public abstract void dispose();<br> | ||
| 439 : | </code><code></code><code>}</code><br> | ||
| 440 : | </div> | ||
| 441 : | <br> | ||
| 442 : | In the component framework, this simplified Site interface is called | ||
| 443 : | IContainer. We now have a highly extensible Site interface, however the | ||
| 444 : | same versioning problems apply to the view as well. The parent of the | ||
| 445 : | view may want to access methods on the View, but we don't want to be | ||
| 446 : | continually adding things to the View base class. For this reason, we | ||
| 447 : | communicate with the view through adapters as well.<br> | ||
| 448 : | <br> | ||
| 449 : | This is the core pattern behind the component framework. It consists of:<br> | ||
| 450 : | <ol> | ||
| 451 : | <li>An extension point that allows 3rd party plugins to contribute | ||
| 452 : | interfaces to components (views and their sites)<br> | ||
| 453 : | </li> | ||
| 454 : | <li>A factory that allows components to be created through | ||
| 455 : | constructor injection</li> | ||
| 456 : | <li>A container interface that manages the lifecycle of components | ||
| 457 : | and their adapters </li> | ||
| 458 : | </ol> | ||
| 459 : | <div style="margin-left: 40px;"></div> | ||
| 460 : | <h2><a class="mozTocH2" name="mozTocId340323"></a>1.2 Requirements<br> | ||
| 461 : | </h2> | ||
| 462 : | sxenos | 1.1 | Primary goals:<br> |
| 463 : | <ul> | ||
| 464 : | <li>Robustness / leak proofing</li> | ||
| 465 : | <li>Nesting of components</li> | ||
| 466 : | <li>Allow components to be reused outside the workbench</li> | ||
| 467 : | <li>Scalability (allow an open-ended set of components)</li> | ||
| 468 : | <li>API versioning<br> | ||
| 469 : | </li> | ||
| 470 : | <li>Ease of use</li> | ||
| 471 : | </ul> | ||
| 472 : | sxenos | 1.2 | <h3><a class="mozTocH2" name="mozTocId29093"></a>1.2.1 Robustness / |
| 473 : | leak | ||
| 474 : | sxenos | 1.1 | proofing<br> |
| 475 : | sxenos | 1.2 | </h3> |
| 476 : | sxenos | 1.1 | Much of the Eclipse API is currently accessed through singleton |
| 477 : | objects. This makes it easy for a view or editor to leak listeners, | ||
| 478 : | fail to clean up reference counts, leak OS resources, etc. since there | ||
| 479 : | is no way of tracking which resources were allocated by a particular | ||
| 480 : | view. This problem would be reduced if views and editors were more like | ||
| 481 : | mini-applications. The view or editor would access the rest of the | ||
| 482 : | sxenos | 1.2 | world through a set of local interfaces. When the view or editor is |
| 483 : | destroyed, all the interfaces would be notified giving them a chance to | ||
| 484 : | clean up any leaked resources.<br> | ||
| 485 : | sxenos | 1.1 | <br> |
| 486 : | For example, instead of reaching into a global | ||
| 487 : | preference store, a view or editor could access all preferences through | ||
| 488 : | sxenos | 1.2 | a local preference adapter. A unique instance of the local preference |
| 489 : | adapter would be | ||
| 490 : | sxenos | 1.1 | created for each view, and would be disposed with the view. When the |
| 491 : | sxenos | 1.2 | preference adapter is destroyed it would clear its listener list, |
| 492 : | sxenos | 1.1 | ensuring that the view will not leak any global listeners. This example |
| 493 : | shows what we mean by a component. Essentially, a | ||
| 494 : | component is an object that communicates with the rest of the | ||
| 495 : | application through a set of interfaces given to it in its constructor.<br> | ||
| 496 : | <span style="font-weight: bold;"></span> | ||
| 497 : | sxenos | 1.2 | <h3><a class="mozTocH2" name="mozTocId223982"></a>1.2.2 Nesting of |
| 498 : | components</h3> | ||
| 499 : | sxenos | 1.1 | There is demand for the ability to embed views and editors inside one |
| 500 : | another. Some examples:<br> | ||
| 501 : | <ul> | ||
| 502 : | <li>An XML editor might embed the properties view</li> | ||
| 503 : | <li>A refactoring wizard might include a source editor</li> | ||
| 504 : | <li>A plugin may wish to create a set of pluggable UI components that | ||
| 505 : | are not views or editors themselves, but can be used inside any view or | ||
| 506 : | editor</li> | ||
| 507 : | <li>Various workbench objects (like the PartSashContainer that | ||
| 508 : | handles the layout of docked parts within the workbench) could be | ||
| 509 : | exposed as API<br> | ||
| 510 : | </li> | ||
| 511 : | </ul> | ||
| 512 : | Many downstream plugins have solved these problems by creating their | ||
| 513 : | sxenos | 1.2 | own frameworks for reusable UI components. Unfortunately, this only |
| 514 : | works | ||
| 515 : | sxenos | 1.1 | for specific views and editors, does not encourage interoperability |
| 516 : | between plugins that have adopted different frameworks, and requires a | ||
| 517 : | lot of work. The goal here is to adopt a framework in the workbench | ||
| 518 : | itself that allows all editors, views, and other workbench objects to | ||
| 519 : | be easily nested.<br> | ||
| 520 : | sxenos | 1.2 | <h3><a class="mozTocH2" name="mozTocId634024"></a>1.2.3 Creating parts |
| 521 : | outside the workbench</h3> | ||
| 522 : | sxenos | 1.1 | It should be possible to instantiate views and editors outside the |
| 523 : | workbench. Some examples:<br> | ||
| 524 : | <ul> | ||
| 525 : | <li>Unit-test editors and views by instantiating them within a JUnit | ||
| 526 : | test suite.</li> | ||
| 527 : | <li>Create an RCP application that does not depend on the workbench | ||
| 528 : | but includes view-like pluggable parts | ||
| 529 : | that could also be used as views within Eclipse</li> | ||
| 530 : | </ul> | ||
| 531 : | sxenos | 1.2 | <h3><a class="mozTocH2" name="mozTocId800379"></a>1.2.4 Scalability</h3> |
| 532 : | The workbench currently offers a closed interface to parts. This | ||
| 533 : | sxenos | 1.1 | forces parts to reach to global objects whenever they need something |
| 534 : | that isn't available from their site. It should be possible for any | ||
| 535 : | plugin to contribute to the set of local API available to a part, and | ||
| 536 : | it | ||
| 537 : | should be possible to instantiate a part even if its parent doesn't | ||
| 538 : | know about all of a part's dependencies.<br> | ||
| 539 : | sxenos | 1.2 | <h3><a class="mozTocH2" name="mozTocId866479"></a>1.2.5 Ease of use<br> |
| 540 : | </h3> | ||
| 541 : | sxenos | 1.1 | Views and editors currently have a complicated lifecycle that must be |
| 542 : | managed by the workbench. This complexity should not be exposed to | ||
| 543 : | client code. <br> | ||
| 544 : | <ul> | ||
| 545 : | <li>It should only require one method call to create a component and | ||
| 546 : | one method call to destroy it.</li> | ||
| 547 : | <li>There should not be unnecessary duplication between XML and java | ||
| 548 : | code.</li> | ||
| 549 : | <li>Components should not | ||
| 550 : | be responsible for dealing with error conditions (such as missing | ||
| 551 : | dependencies) that can be detected by the framework.</li> | ||
| 552 : | <li>Components should not need to implement interfaces they don't | ||
| 553 : | care about.</li> | ||
| 554 : | <li>A parent context should not need to provide child components with | ||
| 555 : | interfaces it doesn't care about.<span style="font-weight: bold;"></span></li> | ||
| 556 : | </ul> | ||
| 557 : | <h1><a class="mozTocH1" name="mozTocId416167"></a>2.0 Component | ||
| 558 : | Framework<br> | ||
| 559 : | </h1> | ||
| 560 : | sxenos | 1.2 | First, some terminology. The pluggable objects that clients write are |
| 561 : | called "components". For example, the class that contains the | ||
| 562 : | implementation of a view itself would be called a component. A | ||
| 563 : | component exists inside an IContainer. In the case of editors and | ||
| 564 : | views, IContainer replaces the existing IWorkbenchSite. Unlike | ||
| 565 : | IWorkbenchSite, the component never needs to directly access the | ||
| 566 : | IContainer since all of its useful interface is provided through | ||
| 567 : | adapters. Instead, the component supplies a constructor that takes all | ||
| 568 : | the interfaces it needs, and it communicates with the outside world | ||
| 569 : | through these interfaces.<br> | ||
| 570 : | <br> | ||
| 571 : | Components are built using constructor | ||
| 572 : | sxenos | 1.1 | injection. Constructor injection means that the framework looks at the |
| 573 : | object's constructor to determine what it needs to do to build that | ||
| 574 : | object.<br> | ||
| 575 : | <br> | ||
| 576 : | Components:<br> | ||
| 577 : | <ul> | ||
| 578 : | <li>Have exactly one constructor</li> | ||
| 579 : | <li>Take zero or more other components as arguments to their | ||
| 580 : | constructor</li> | ||
| 581 : | <li>Are fully initialized by their constructor</li> | ||
| 582 : | </ul> | ||
| 583 : | Components do not:<br> | ||
| 584 : | <ul> | ||
| 585 : | <li>Take arrays or primitives as arguments to their constructor</li> | ||
| 586 : | <li>Take more than one argument of the same type in their constructor<br> | ||
| 587 : | </li> | ||
| 588 : | <li>Need to support any particular base class or interface</li> | ||
| 589 : | </ul> | ||
| 590 : | Any class with these properties can be used as a component. Since | ||
| 591 : | components are plain-old-java-objects, they do not need to depend on | ||
| 592 : | the component framework. Components are fully initialized by | ||
| 593 : | their constructor, meaning it is never necessary | ||
| 594 : | to call an initialize method or any combination of set methods after | ||
| 595 : | constructing the object. Components never accept null as an argument | ||
| 596 : | to their constructor.<br> | ||
| 597 : | <br> | ||
| 598 : | For example, if a view could be created as a component it might look | ||
| 599 : | like this:<br> | ||
| 600 : | <br> | ||
| 601 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 602 : | sxenos | 1.2 | <code> * "Hello world" view using the component framework</code><br> |
| 603 : | sxenos | 1.1 | <code> */</code><br> |
| 604 : | <code>public class HelloWorldView {</code><br> | ||
| 605 : | <code> public HelloWorldView(Composite parent) {</code><br> | ||
| 606 : | <code> Label helloWorld = new | ||
| 607 : | Label(parent, SWT.NONE);</code><br> | ||
| 608 : | <code> | ||
| 609 : | helloWorld.setText("Hello | ||
| 610 : | world");</code><br> | ||
| 611 : | <code> }</code><br> | ||
| 612 : | <code>}</code><br> | ||
| 613 : | <code></code></div> | ||
| 614 : | <code><br> | ||
| 615 : | sxenos | 1.2 | </code>The HelloWorldView component depends on one other object: a |
| 616 : | Composite | ||
| 617 : | sxenos | 1.1 | created for it by whoever instantiated the view. Compare this with the |
| 618 : | same view written using the existing API:<br> | ||
| 619 : | <br> | ||
| 620 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 621 : | <code> * "hello world" view using the Eclipse 3.0 API.</code><br> | ||
| 622 : | <code> */</code><br> | ||
| 623 : | <code>public class HelloWorldView extends ViewPart {</code><br> | ||
| 624 : | <code> public void createPartControl(Composite | ||
| 625 : | parent) {</code><br> | ||
| 626 : | <code></code><code> Label | ||
| 627 : | helloWorld = new Label(parent, SWT.NONE);</code><br> | ||
| 628 : | <code> | ||
| 629 : | helloWorld.setText("Hello | ||
| 630 : | world");</code><code> </code><br> | ||
| 631 : | <code> }</code><br> | ||
| 632 : | <code></code><br> | ||
| 633 : | <code> public void setFocus() {</code><br> | ||
| 634 : | <code> }</code><br> | ||
| 635 : | <code>}</code><br> | ||
| 636 : | </div> | ||
| 637 : | <br> | ||
| 638 : | The main difference is that the component version does not require the | ||
| 639 : | ViewPart base class and is fully initialized after construction. In | ||
| 640 : | both cases, the extension point markup would look like this:<br> | ||
| 641 : | <br> | ||
| 642 : | <div style="margin-left: 40px;"><code><extension | ||
| 643 : | point="org.eclipse.ui.views"></code><br> | ||
| 644 : | <code> <view</code><br> | ||
| 645 : | <code> name="Title Test View"</code><br> | ||
| 646 : | <code> icon="icons\view.gif"</code><br> | ||
| 647 : | <code> | ||
| 648 : | class="org.eclipse.ui.mytest.HelloWorldView"</code><br> | ||
| 649 : | <code> id="</code><code>org.eclipse.ui.mytest.HelloWorldView</code><code>ID"></code><br> | ||
| 650 : | <code> </view></code><br> | ||
| 651 : | <code></extension></code><br> | ||
| 652 : | </div> | ||
| 653 : | <br> | ||
| 654 : | <h2><a class="mozTocH2" name="mozTocId489102"></a>2.1 Instantiating a | ||
| 655 : | view<br> | ||
| 656 : | </h2> | ||
| 657 : | Components are instantiated using factories. For the moment, let's | ||
| 658 : | focus on views before we explore components in general. Views could be | ||
| 659 : | created using a method on IWorkbenchPage that would look something like | ||
| 660 : | this. Note that this example is only intended to illustrate the general | ||
| 661 : | idea -- the actual protocol for creating views is likely to change.<br> | ||
| 662 : | <br> | ||
| 663 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 664 : | <code> * Creates a view of the given type inside the given | ||
| 665 : | composite. | ||
| 666 : | The caller must dispose the container once they are done with it.</code><br> | ||
| 667 : | <code> *</code><br> | ||
| 668 : | <code> * @param viewId id of the view extension to use</code><br> | ||
| 669 : | <code> * @param parentComposite parent composite for the view</code><br> | ||
| 670 : | <code> * @return an IContainer that contains all components needed | ||
| 671 : | for the view</code><br> | ||
| 672 : | <code> */</code><br> | ||
| 673 : | <code>IContainer createView(String viewId, Composite parentComposite);</code><br> | ||
| 674 : | <code></code></div> | ||
| 675 : | <code></code><br> | ||
| 676 : | <br> | ||
| 677 : | This creates a new instance of the view in the given composite. Notice | ||
| 678 : | that the factory method doesn't return an instance of the view itself | ||
| 679 : | but an instance of IContainer, which looks something like this:<br> | ||
| 680 : | <br> | ||
| 681 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 682 : | <code> * Main interface to a component.</code><br> | ||
| 683 : | <code> */</code><br> | ||
| 684 : | <code>public interface IContainer extends IAdaptable {</code><br> | ||
| 685 : | <code></code> public void dispose();<br> | ||
| 686 : | }<br> | ||
| 687 : | </div> | ||
| 688 : | <br> | ||
| 689 : | This interface wraps the component and all of its dependencies. Once | ||
| 690 : | we've obtained an IContainer handle, we are obligated to dispose it | ||
| 691 : | when we are done with it. All access to the component is done through | ||
| 692 : | adapters, which insulates | ||
| 693 : | the application from changes in individual views. For example, if a | ||
| 694 : | view that previously implemented the IViewInterface1 migrates to using | ||
| 695 : | the newer IViewInterface2, its container will continue to work as long | ||
| 696 : | as someone has provided an adapter between the two versions of | ||
| 697 : | sxenos | 1.2 | IViewInterface. The getAdapter() method on IContainer searches for |
| 698 : | sxenos | 1.1 | adapters in the following order:<br> |
| 699 : | <br> | ||
| 700 : | 1. If the component itself implements the adapter type, it returns the | ||
| 701 : | component.<br> | ||
| 702 : | 2. If the component itself implements IAdapter, and the component's | ||
| 703 : | getAdapter(...) method returns non-null, we return that adapter.<br> | ||
| 704 : | 3. If the adapter manager has an adapter between the component and the | ||
| 705 : | adapter type, we return that adapter.<br> | ||
| 706 : | 4. If the container's factory can construct a component of the | ||
| 707 : | requested type, we | ||
| 708 : | create that component, add it as a local dependency to the IContainer, | ||
| 709 : | and | ||
| 710 : | return it.<br> | ||
| 711 : | 5. Return null<br> | ||
| 712 : | <br> | ||
| 713 : | For example, it would be possible to create our HelloWorldView (above) | ||
| 714 : | in a modal dialog like this:<br> | ||
| 715 : | <br> | ||
| 716 : | <div style="margin-left: 40px;"><code>void | ||
| 717 : | createHelloWorldViewInADialog(IWorkbenchPage page) {</code><br> | ||
| 718 : | <code> Display display = Display.getDefault()</code><br> | ||
| 719 : | <code> Shell shell = new Shell(Display.getDefault());</code><br> | ||
| 720 : | <code></code><br> | ||
| 721 : | <code> shell.setLayout(new FillLayout());</code><br> | ||
| 722 : | <code></code><br> | ||
| 723 : | <code> // Create the view and its widgets</code><br> | ||
| 724 : | <code> IContainer myView = | ||
| 725 : | workbenchPage.createView("org.eclipse.ui.mytest.HelloWorldViewID", | ||
| 726 : | shell);</code><br> | ||
| 727 : | <code></code><br> | ||
| 728 : | <code> shell.open();</code><br> | ||
| 729 : | <code></code><br> | ||
| 730 : | <code> while (!shell.isDisposed()) {</code><br> | ||
| 731 : | <code> if (!display.readAndDispatch ()) | ||
| 732 : | display.sleep ();</code><br> | ||
| 733 : | <code> }</code><br> | ||
| 734 : | <code></code><br> | ||
| 735 : | <code> // Dispose the view</code><br> | ||
| 736 : | <code> myView.dispose();</code><br> | ||
| 737 : | <code>}</code><br> | ||
| 738 : | <code></code></div> | ||
| 739 : | <h2><a class="mozTocH2" name="mozTocId717633"></a>2.2 Instantiating | ||
| 740 : | components in general<br> | ||
| 741 : | </h2> | ||
| 742 : | All components are constructed using an IContainerFactory, however most | ||
| 743 : | component-based extension points will provide some | ||
| 744 : | sort of convenience method to wrap their IContainerFactory. The | ||
| 745 : | IWorkbenchPage.createView method in the | ||
| 746 : | previous section is a convenience method for the <span | ||
| 747 : | style="font-style: italic;">org.eclipse.ui.views </span>extension | ||
| 748 : | point.<br style="font-style: italic;"> | ||
| 749 : | <br> | ||
| 750 : | IContainerFactory looks like this:<br> | ||
| 751 : | <br> | ||
| 752 : | <div style="margin-left: 40px;"><code>/**<br> | ||
| 753 : | * Factory for IContainer instances. The default factory is | ||
| 754 : | returned by<br> | ||
| 755 : | * Components.getFactory(). Clients wishing to implement their own | ||
| 756 : | specialized <br> | ||
| 757 : | * factories should call createDerivedFactory() to get access to a | ||
| 758 : | factory<br> | ||
| 759 : | * whose behavior can be modified programmatically. Not intended | ||
| 760 : | to be <br> | ||
| 761 : | * implemented by clients. <br> | ||
| 762 : | * <br> | ||
| 763 : | * @since 3.1<br> | ||
| 764 : | */<br> | ||
| 765 : | public interface IContainerFactory {<br> | ||
| 766 : | <br> | ||
| 767 : | /**<br> | ||
| 768 : | * Creates and returns a new IContainer | ||
| 769 : | instance, given the <br> | ||
| 770 : | * implementation class for its component. The | ||
| 771 : | caller MUST call IContainer.dispose() <br> | ||
| 772 : | * when it is done with the component. The | ||
| 773 : | factory does not need any prior<br> | ||
| 774 : | sxenos | 1.2 | * knowledge of the component class.<br> |
| 775 : | sxenos | 1.1 | * <br> |
| 776 : | * @param componentImplementation concrete | ||
| 777 : | class to be instantiated by the factory. The class<br> | ||
| 778 : | * | ||
| 779 : | must be a valid component (it must have exactly one constructor which | ||
| 780 : | only<br> | ||
| 781 : | * | ||
| 782 : | references other component interfaces known to this factory).<br> | ||
| 783 : | * @return a newly constructed | ||
| 784 : | <code>IContainer</code> instance<br> | ||
| 785 : | sxenos | 1.2 | * @throws CoreException if unable to create |
| 786 : | the component<br> | ||
| 787 : | sxenos | 1.1 | */<br> |
| 788 : | public IContainer createContainer(Class | ||
| 789 : | sxenos | 1.2 | componentImplementation) throws CoreException;<br> |
| 790 : | sxenos | 1.1 | <br> |
| 791 : | /**<br> | ||
| 792 : | * Creates a specialization of this factory. By | ||
| 793 : | default, the specialized<br> | ||
| 794 : | * factory will have the same behavior as its | ||
| 795 : | parent. However, the derived<br> | ||
| 796 : | * factory can add, or override the | ||
| 797 : | implementation for any components.<br> | ||
| 798 : | * <br> | ||
| 799 : | * @return new factory instance that allows | ||
| 800 : | individual components to be overridden.<br> | ||
| 801 : | * By default, the derived factory will | ||
| 802 : | delegate all of its behavior to the receiver.<br> | ||
| 803 : | * Changes in the receiver will affect the | ||
| 804 : | derived factory.<br> | ||
| 805 : | */<br> | ||
| 806 : | public IMutableContainerFactory | ||
| 807 : | createDerivedFactory();<br> | ||
| 808 : | }</code><code></code><br> | ||
| 809 : | <code></code></div> | ||
| 810 : | <code></code><br> | ||
| 811 : | All arguments to a component's constructor are provided by its factory. | ||
| 812 : | Different | ||
| 813 : | factories know how to create different types of components or will | ||
| 814 : | provide | ||
| 815 : | different implementations for the same components. All factories are | ||
| 816 : | derived from the root factory, which is returned by | ||
| 817 : | Components.getFactory(). A plugin can create derived factories to | ||
| 818 : | sxenos | 1.2 | supply alternative adapter implementations to the components it creates.<br> |
| 819 : | <br> | ||
| 820 : | In | ||
| 821 : | sxenos | 1.1 | our case, we will create a container factory to pass a specific |
| 822 : | Composite into the constructor of HelloWorldView. The code looks like | ||
| 823 : | this:<br> | ||
| 824 : | <code></code><code><br> | ||
| 825 : | </code> | ||
| 826 : | <div style="margin-left: 40px;"><code>// Get the global factory</code><br> | ||
| 827 : | <code>IContainerFactory viewFactory = | ||
| 828 : | Components.getFactory();</code><br> | ||
| 829 : | <code></code><br> | ||
| 830 : | <code>// Create the Composite for the view</code><br> | ||
| 831 : | <code></code><code>Composite viewComposite = new | ||
| 832 : | Composite(parentComposite, SWT.NONE);</code><br> | ||
| 833 : | <code>viewComposite.setLayout(new FillLayout();</code><br> | ||
| 834 : | <code></code><br> | ||
| 835 : | <code>// Create a specialized factory that knows about the view's | ||
| 836 : | composite and plugin bundle</code><br> | ||
| 837 : | <code>IMutableContainerFactory derivedFactory = | ||
| 838 : | viewFactory.createDerivedFactory();</code><br> | ||
| 839 : | <code></code><br> | ||
| 840 : | sxenos | 1.2 | <code>// Add the view's composite to the factory so that it can be seen |
| 841 : | by the view constructor<br> | ||
| 842 : | </code><code>derivedFactory.addComponentInstance(viewComposite);</code><br> | ||
| 843 : | sxenos | 1.1 | <code></code><br> |
| 844 : | sxenos | 1.2 | <code>// Add the view's plugin bundle to the factory (not |
| 845 : | sxenos | 1.1 | required in this example, but this</code><br> |
| 846 : | <code>// is recommended practise for any component created from an | ||
| 847 : | extension point).</code><br> | ||
| 848 : | <code>derivedFactory.addComponentInstance(pluginBundle);</code><br> | ||
| 849 : | <code></code><br> | ||
| 850 : | <code>// Create the view. Provide its constructor and some | ||
| 851 : | context (the page that created it).</code><br> | ||
| 852 : | <code>IContainer view = | ||
| 853 : | derivedFactory.createContainer(HelloWorldView.class);</code><br> | ||
| 854 : | <code></code><br> | ||
| 855 : | <code>// Do something with the view</code><br> | ||
| 856 : | <code>// ...</code><br> | ||
| 857 : | <code></code><br> | ||
| 858 : | <code>// Now clean up</code><br> | ||
| 859 : | <code>view.dispose();</code><br> | ||
| 860 : | <code>viewComposite.dispose();</code><br> | ||
| 861 : | <code></code></div> | ||
| 862 : | <code><br> | ||
| 863 : | </code><br> | ||
| 864 : | This code will work, but it has two problems:<br> | ||
| 865 : | <br> | ||
| 866 : | 1. Even if HelloWorldView didn't require a Composite, we would still | ||
| 867 : | have created one (which is wasteful).<br> | ||
| 868 : | 2. We need to manually dispose the view's composite after we're done | ||
| 869 : | sxenos | 1.2 | with it. This defeats the point of IContainer.dispose(), which is |
| 870 : | sxenos | 1.1 | supposed to clean up all of the component's dependencies automatically.<br> |
| 871 : | <h2><a class="mozTocH2" name="mozTocId450231"></a>2.3 Creating | ||
| 872 : | dependent components on demand </h2> | ||
| 873 : | Rather than managing the view's Composite ourselves, we could supply a | ||
| 874 : | factory that knows how to create and destroy Composites as needed. Such | ||
| 875 : | a factory would look like this:<br> | ||
| 876 : | <br> | ||
| 877 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 878 : | sxenos | 1.2 | <code> * ComponentAdapter for creating and managing SWT Composites.</code><br> |
| 879 : | sxenos | 1.1 | <code> */</code><br> |
| 880 : | <code>public class CompositeFactory extends ComponentAdapter {</code><br> | ||
| 881 : | <code></code><br> | ||
| 882 : | <code> private Composite parent;</code><br> | ||
| 883 : | <code> </code><br> | ||
| 884 : | <code> /**</code><br> | ||
| 885 : | <code> * The SWT composites created by this | ||
| 886 : | factory will all be children of the given</code><br> | ||
| 887 : | <code> * composite.</code><br> | ||
| 888 : | <code> */</code><br> | ||
| 889 : | <code> public CompositeFactory(Composite parent) {</code><br> | ||
| 890 : | <code> this.parent = parent;</code><br> | ||
| 891 : | <code> }</code><br> | ||
| 892 : | <code> </code><br> | ||
| 893 : | <code> protected Object create(IAdaptable | ||
| 894 : | sxenos | 1.2 | availableAdapters) throws CoreException {</code><br> |
| 895 : | <code> // Create a new Composite</code><code></code><br> | ||
| 896 : | sxenos | 1.1 | <code> Composite newChild = |
| 897 : | new Composite(parent, SWT.NONE);</code><br> | ||
| 898 : | <code> newChild.setLayout(new | ||
| 899 : | FillLayout());</code><br> | ||
| 900 : | <code> return newChild;</code><br> | ||
| 901 : | <code> }</code><br> | ||
| 902 : | <code></code><br> | ||
| 903 : | sxenos | 1.2 | <code> protected void dispose(Object toDispose) {</code><br> |
| 904 : | sxenos | 1.1 | <code> |
| 905 : | sxenos | 1.2 | ((Composite)toDispose).dispose();</code><br> |
| 906 : | sxenos | 1.1 | <code> }</code><br> |
| 907 : | <code></code><br> | ||
| 908 : | <code> public String getInterfaceName() {</code><br> | ||
| 909 : | <code> // Return the fully | ||
| 910 : | qualified class name of the Composite class. The framework will call</code><br> | ||
| 911 : | <code> // this method to determine | ||
| 912 : | sxenos | 1.2 | what type of interface will be implemented by the objects <br> |
| 913 : | // constructed by this factory.</code><br> | ||
| 914 : | sxenos | 1.1 | <code> return |
| 915 : | Composite.class.getName();</code><br> | ||
| 916 : | <code> }</code><br> | ||
| 917 : | <code>}</code><br> | ||
| 918 : | <code></code></div> | ||
| 919 : | <br> | ||
| 920 : | Using CompositeFactory, we could construct HelloWorldView like this:<br> | ||
| 921 : | <br> | ||
| 922 : | <div style="margin-left: 40px;"><code>// Get the global factory</code><br> | ||
| 923 : | <code>IContainerFactory viewFactory = | ||
| 924 : | Components.getFactory();</code><code></code><br> | ||
| 925 : | <code></code><br> | ||
| 926 : | <code>// Create a specialized factory that knows about the view's | ||
| 927 : | composite and plugin bundle</code><br> | ||
| 928 : | <code>IMutableContainerFactory derivedFactory = | ||
| 929 : | viewFactory.createDerivedFactory();</code><br> | ||
| 930 : | <code></code><br> | ||
| 931 : | sxenos | 1.2 | <code>// Add a factory that can create the view's composite</code><br> |
| 932 : | sxenos | 1.1 | <code>derivedFactory.addComponentFactory(new |
| 933 : | CompositeFactory(parentComposite));</code><br> | ||
| 934 : | <code></code><br> | ||
| 935 : | sxenos | 1.2 | <code>// Add the view's plugin bundle to the factory (not |
| 936 : | sxenos | 1.1 | required in this example, but this</code><br> |
| 937 : | <code>// is recommended practise for any component created from an | ||
| 938 : | extension point).</code><br> | ||
| 939 : | <code>derivedFactory.addComponentInstance(pluginBundle);</code><br> | ||
| 940 : | <code></code><br> | ||
| 941 : | <code>// Create the view. Provide its constructor and some | ||
| 942 : | context (the page that created it).</code><br> | ||
| 943 : | <code>IContainer view = | ||
| 944 : | derivedFactory.createComponent(HelloWorldView.class);</code><br> | ||
| 945 : | <code></code><br> | ||
| 946 : | <code>// Do something with the view</code><br> | ||
| 947 : | <code>// ...</code><br> | ||
| 948 : | <code></code><br> | ||
| 949 : | <code>// Now clean up</code><br> | ||
| 950 : | <code>view.dispose();</code><br> | ||
| 951 : | <code></code></div> | ||
| 952 : | <code> | ||
| 953 : | </code><br> | ||
| 954 : | The composite will now be allocated as needed and disposed inside the | ||
| 955 : | call to view.dispose().<br> | ||
| 956 : | <br> | ||
| 957 : | <code></code> | ||
| 958 : | sxenos | 1.2 | <h2><a class="mozTocH2" name="mozTocId928619"></a>2.4 Declaring |
| 959 : | Component Interfaces</h2> | ||
| 960 : | The types that a component receives in its constructor are called | ||
| 961 : | component interfaces. Component interfaces are registered through the <span | ||
| 962 : | style="font-style: italic;">org.eclipse.core.component.interface</span> | ||
| 963 : | extension point, and each has a default implementation. When a | ||
| 964 : | component is | ||
| 965 : | created, its parent may provide an implementation of any service | ||
| 966 : | it knows about. If the component requests an interface that isn't | ||
| 967 : | provided explicitly by the parent, the default implementation is used.<br> | ||
| 968 : | sxenos | 1.1 | <br> |
| 969 : | sxenos | 1.2 | Here is an |
| 970 : | example component interface:<br> | ||
| 971 : | sxenos | 1.1 | <code><br> |
| 972 : | </code> | ||
| 973 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 974 : | sxenos | 1.2 | <code> * Component interface: </code><br> |
| 975 : | sxenos | 1.1 | <code> *</code><br> |
| 976 : | <code> * Provides a context for reporting and logging exceptions.</code><br> | ||
| 977 : | <code> */</code><br> | ||
| 978 : | <code>public interface IErrorContext {</code><br> | ||
| 979 : | <code> /**</code><br> | ||
| 980 : | <code> * Create an IStatus error describing the | ||
| 981 : | given | ||
| 982 : | throwable</code><br> | ||
| 983 : | <code> */</code><br> | ||
| 984 : | <code> public IStatus createStatus(Throwable t);</code><br> | ||
| 985 : | <code></code><br> | ||
| 986 : | <code> /**</code><br> | ||
| 987 : | <code> * Create an IStatus message with the | ||
| 988 : | given | ||
| 989 : | severity, message, and (optional) throwable</code><br> | ||
| 990 : | <code> */</code><br> | ||
| 991 : | <code> public IStatus createStatus(int severity, | ||
| 992 : | String | ||
| 993 : | message, Throwable t);</code><br> | ||
| 994 : | <code></code><br> | ||
| 995 : | <code> /**</code><br> | ||
| 996 : | <code> * Logs an IStatus message</code><br> | ||
| 997 : | <code> */</code><br> | ||
| 998 : | <code> public void log(IStatus status);</code><br> | ||
| 999 : | <code></code><br> | ||
| 1000 : | <code> /**</code><br> | ||
| 1001 : | <code> * Logs an exception to the system log</code><br> | ||
| 1002 : | <code> */</code><br> | ||
| 1003 : | <code> public void log(Throwable t);</code><br> | ||
| 1004 : | <code>}</code><br> | ||
| 1005 : | <code></code></div> | ||
| 1006 : | <code><br> | ||
| 1007 : | <br> | ||
| 1008 : | sxenos | 1.2 | </code>Here is the associated default implementation:<br> |
| 1009 : | sxenos | 1.1 | <code><br> |
| 1010 : | </code> | ||
| 1011 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 1012 : | sxenos | 1.2 | <code> * Default implementation of the IErrorContext interface:</code><br> |
| 1013 : | sxenos | 1.1 | <code> *</code><br> |
| 1014 : | <code> * Creates and logs errors in the context of a plugin bundle</code><br> | ||
| 1015 : | <code> */</code><br> | ||
| 1016 : | <code>public class ErrorContext implements IErrorContext {</code><br> | ||
| 1017 : | <code> private Bundle pluginBundle;</code><br> | ||
| 1018 : | <code></code><br> | ||
| 1019 : | sxenos | 1.2 | <code> public ErrorContext(Bundle |
| 1020 : | sxenos | 1.1 | pluginBundle) {</code><br> |
| 1021 : | <code> this.pluginBundle = | ||
| 1022 : | pluginBundle;</code><br> | ||
| 1023 : | <code> }</code><br> | ||
| 1024 : | <code></code><br> | ||
| 1025 : | <code> public IStatus createStatus(Throwable t) {</code><br> | ||
| 1026 : | <code> String message = t.getMessage();</code><br> | ||
| 1027 : | <code> if (message == null) {</code><br> | ||
| 1028 : | <code> message = | ||
| 1029 : | t.getString();</code><br> | ||
| 1030 : | <code> }</code><br> | ||
| 1031 : | <code> return createStatus(Status.ERROR, | ||
| 1032 : | message, t);</code><br> | ||
| 1033 : | <code> }</code><br> | ||
| 1034 : | <code></code><br> | ||
| 1035 : | <code> public IStatus createStatus(int severity, | ||
| 1036 : | String | ||
| 1037 : | message, Throwable t) {</code><br> | ||
| 1038 : | <code> return new Status(severity, | ||
| 1039 : | pluginBundle.getSymbolicName(), Status.OK, message, t);</code><br> | ||
| 1040 : | <code> }</code><br> | ||
| 1041 : | <code></code><br> | ||
| 1042 : | <code> public void log(Throwable t) {</code><br> | ||
| 1043 : | <code> log(createStatus(t));</code><br> | ||
| 1044 : | <code> }</code><br> | ||
| 1045 : | <code></code><br> | ||
| 1046 : | <code> public void log(IStatus status) {</code><br> | ||
| 1047 : | <code> Plugin plugin = | ||
| 1048 : | Platform.getPlugin(pluginBundle.getSymbolicName());</code><br> | ||
| 1049 : | <code> plugin.getLog().log(status);</code><br> | ||
| 1050 : | <code> }</code><br> | ||
| 1051 : | <code>}</code><br> | ||
| 1052 : | <code></code></div> | ||
| 1053 : | <code></code><br> | ||
| 1054 : | Finally, here is the extension markup:<br> | ||
| 1055 : | <br> | ||
| 1056 : | <div style="margin-left: 40px;"><code><extension | ||
| 1057 : | sxenos | 1.2 | point="org.eclipse.core.component.interface"></code><br> |
| 1058 : | <code> <interface | ||
| 1059 : | sxenos | 1.1 | </code><br> |
| 1060 : | <code> | ||
| 1061 : | class="org.eclipse.ui.workbench.ErrorContext"</code><br> | ||
| 1062 : | sxenos | 1.2 | <code> </code><code>interface="</code><code>org.eclipse.ui.workbench.IErrorContext</code><code>"<br> |
| 1063 : | childadapter="false"<br> | ||
| 1064 : | </code><code> scope="plugin"></code><br> | ||
| 1065 : | <code> </interface></code><br> | ||
| 1066 : | sxenos | 1.1 | <code></extension></code><br> |
| 1067 : | </div> | ||
| 1068 : | <br> | ||
| 1069 : | Notice that the extension XML needs to indicate which service interface | ||
| 1070 : | is being implemented by the service, but not its dependencies. When it | ||
| 1071 : | comes time to create the service, the framework detects that the | ||
| 1072 : | ErrorContext needs to be associated with a plugin Bundle by examining | ||
| 1073 : | its constructor. This means that if plugin A defines the ErrorContext | ||
| 1074 : | service and plugin B defines a view that uses the service, then all of | ||
| 1075 : | the status messages constructed by the view will be associated with the | ||
| 1076 : | view's own plugin - not the plugin that defined the ErrorContext | ||
| 1077 : | service.<br> | ||
| 1078 : | sxenos | 1.2 | <br> |
| 1079 : | The <span style="font-style: italic;">scope</span> attribute is a path | ||
| 1080 : | indicating how much context is needed to create the service. The <span | ||
| 1081 : | style="font-style: italic;">plugin </span>scope indicates that this | ||
| 1082 : | service is associated with a plugin, which is why it is allowed to take | ||
| 1083 : | a Bundle in its constructor. More on scopes later.<br> | ||
| 1084 : | <br> | ||
| 1085 : | The <span style="font-style: italic;">childadapter="false" </span>tag | ||
| 1086 : | is optional. The default value, false, indicates that a component can | ||
| 1087 : | take the interface in its constructor and/or supply an alternative | ||
| 1088 : | implementation to its children. If <span style="font-style: italic;">childadapter="true"</span> | ||
| 1089 : | were specified, the component would be allowed to implement the | ||
| 1090 : | interface or request it from its children. Essentially, this determines | ||
| 1091 : | if the interface is intended for top-down or bottom-up communication | ||
| 1092 : | between a component and its parent.<br> | ||
| 1093 : | <br> | ||
| 1094 : | Plugins are not allowed to declare services using interfaces defined in | ||
| 1095 : | another plugin. This prevents two plugins from defining a service using | ||
| 1096 : | the same interface, and ensures that circular dependencies can only | ||
| 1097 : | occur within the same plugin.<br> | ||
| 1098 : | <h2><a class="mozTocH2" name="mozTocId749133"></a>2.5 Using Component | ||
| 1099 : | Interfaces<br> | ||
| 1100 : | </h2> | ||
| 1101 : | This example shows how a component can use an interface supplied by its | ||
| 1102 : | parent.<br> | ||
| 1103 : | sxenos | 1.1 | <br> |
| 1104 : | <div style="margin-left: 40px;"><code>public class MyView {</code><br> | ||
| 1105 : | <code> private IErrorContext errorContext;</code><br> | ||
| 1106 : | <code></code><br> | ||
| 1107 : | <code> public MyView(</code><code>Composite parent, </code><code>IErrorContext | ||
| 1108 : | errorContext) {</code><br> | ||
| 1109 : | <code> this.errorContext = errorContext;</code><br> | ||
| 1110 : | <code></code><br> | ||
| 1111 : | <code></code><code> Button errorButton = | ||
| 1112 : | new | ||
| 1113 : | Button(parent, SWT.PUSH);</code><br> | ||
| 1114 : | <code> errorButton.setText("Log an | ||
| 1115 : | exception");</code><br> | ||
| 1116 : | <code> </code><br> | ||
| 1117 : | <code> | ||
| 1118 : | errorButton.addSelectionListener(new | ||
| 1119 : | SelectionAdapter() {</code><br> | ||
| 1120 : | <code> public void | ||
| 1121 : | widgetSelected(SelectionEvent e) {</code><br> | ||
| 1122 : | <code> | ||
| 1123 : | | ||
| 1124 : | try {</code><br> | ||
| 1125 : | <code> | ||
| 1126 : | | ||
| 1127 : | // Throw a NPE</code><br> | ||
| 1128 : | <code> | ||
| 1129 : | | ||
| 1130 : | String myString = null;</code><br> | ||
| 1131 : | <code> | ||
| 1132 : | | ||
| 1133 : | String bogusCode = myString.substring(10, 30);</code><br> | ||
| 1134 : | <code> | ||
| 1135 : | | ||
| 1136 : | } catch (Exception e) {</code><br> | ||
| 1137 : | <code> | ||
| 1138 : | | ||
| 1139 : | sxenos | 1.2 | // Log the NPE using the error context</code><br> |
| 1140 : | sxenos | 1.1 | <code> |
| 1141 : | errorContext.log(e);</code><br> | ||
| 1142 : | <code> | ||
| 1143 : | }</code><br> | ||
| 1144 : | <code> }</code><br> | ||
| 1145 : | <code> });</code><br> | ||
| 1146 : | <code></code><code> }</code><br> | ||
| 1147 : | <code></code><code>}</code><br> | ||
| 1148 : | <code></code></div> | ||
| 1149 : | <code><br> | ||
| 1150 : | </code>This view creates a button which, when pressed, will throw a NPE | ||
| 1151 : | and log it. The status message will be constructed and logged using the | ||
| 1152 : | sxenos | 1.2 | IErrorContext interface we defined above. The default implementation of |
| 1153 : | the interface is associated with a | ||
| 1154 : | unique component instance, so if we were to create two instances of | ||
| 1155 : | MyView we would also end up with two instances of ErrorContext. <br> | ||
| 1156 : | <br> | ||
| 1157 : | There | ||
| 1158 : | is never any ambiguity about which implementation of IErrorContext to | ||
| 1159 : | use. If the parent of MyView explicitly supplies an IErrorContext | ||
| 1160 : | service, that implementation is used. Otherwise, the unique default | ||
| 1161 : | implementation is used. There can never be two global implementations.<br> | ||
| 1162 : | sxenos | 1.1 | <br> |
| 1163 : | <h2><a class="mozTocH2" name="mozTocId705695"></a>2.6 Lifecycle</h2> | ||
| 1164 : | Many components need to do explicit cleanup. Components | ||
| 1165 : | that need to perform cleanup will implement the | ||
| 1166 : | IDisposable interface. For example, the following component attaches a | ||
| 1167 : | listener to a global preference store and detaches it when done.<br> | ||
| 1168 : | <br> | ||
| 1169 : | <div style="margin-left: 40px;"><code>/**</code><br> | ||
| 1170 : | <code> * Provides access to a plugin's preference store in a | ||
| 1171 : | manner that | ||
| 1172 : | prevents listener leaks.</code><br> | ||
| 1173 : | <code> */</code><br> | ||
| 1174 : | <code>public interface IPreferences {</code><br> | ||
| 1175 : | <code> public String getString(String prefId);</code><br> | ||
| 1176 : | <code> public void setString(String prefId, String | ||
| 1177 : | value);</code><br> | ||
| 1178 : | <code> public void | ||
| 1179 : | addListener(IPropertyChangeListener l);</code><br> | ||
| 1180 : | <code> public void | ||
| 1181 : | removeListener(IPropertyChangeListener | ||
| 1182 : | l);</code><br> | ||
| 1183 : | <code>}</code><br> | ||
| 1184 : | <code></code><br> | ||
| 1185 : | <code>/**</code><br> | ||
| 1186 : | <code> * Concrete implementation of the IPreferences interface</code><br> | ||
| 1187 : | <code> */</code><br> | ||
| 1188 : | <code>public LocalPreferenceStore implements IPreferences, IDisposable {</code><br> | ||
| 1189 : | <code> ListenerList listeners = new ListenerList();</code><br> | ||
| 1190 : | <code> Preferences prefs;</code><br> | ||
| 1191 : | <code> </code><br> | ||
| 1192 : | <code> private class PropertyChangeListener | ||
| 1193 : | implements | ||
| 1194 : | Preferences.IPropertyChangeListener {</code><br> | ||
| 1195 : | <code> /*</code><br> | ||
| 1196 : | <code> * @see | ||
| 1197 : | org.eclipse.core.runtime.Preferences.IPropertyChangeListener#propertyChange(org.eclipse.core.runtime.Preferences.PropertyChangeEvent)</code><br> | ||
| 1198 : | <code> */</code><br> | ||
| 1199 : | <code> public void | ||
| 1200 : | propertyChange(Preferences.PropertyChangeEvent event) {</code><br> | ||
| 1201 : | <code> | ||
| 1202 : | firePropertyChangeEvent(event.getProperty(), event.getOldValue(), | ||
| 1203 : | event.getNewValue());</code><br> | ||
| 1204 : | <code> }</code><br> | ||
| 1205 : | <code> }</code><br> | ||
| 1206 : | <code></code><br> | ||
| 1207 : | <code> PropertyChangeListener listener = new | ||
| 1208 : | PropertyChangeListener();</code><br> | ||
| 1209 : | <code></code><br> | ||
| 1210 : | <code> /**</code><br> | ||
| 1211 : | <code> * Create a wrapper around the given | ||
| 1212 : | bundle's | ||
| 1213 : | preference store.</code><br> | ||
| 1214 : | <code> */</code><br> | ||
| 1215 : | <code> public LocalPreferenceStore(Bundle | ||
| 1216 : | pluginBundle) {</code><br> | ||
| 1217 : | <code> Plugin plugin = | ||
| 1218 : | Platform.getPlugin(pluginBundle.getSymbolicName());</code><br> | ||
| 1219 : | <code> prefs = | ||
| 1220 : | plugin.getPluginPreferences();</code><br> | ||
| 1221 : | <code> prefs.addListener(listener);</code><br> | ||
| 1222 : | <code> }</code><br> | ||
| 1223 : | <code></code><br> | ||
| 1224 : | <code> private void firePropertyChange(String name, | ||
| 1225 : | Object | ||
| 1226 : | oldValue, Object newValue) {</code><br> | ||
| 1227 : | <code> PropertyChangeEvent event = | ||
| 1228 : | new | ||
| 1229 : | PropertyChangeEvent(this, name, oldValue, newValue);</code><br> | ||
| 1230 : | <code> Object[] listeners = | ||
| 1231 : | this.listeners.getListeners();</code><br> | ||
| 1232 : | <code> for (int i= 0; i < | ||
| 1233 : | listeners.length; i++)</code><br> | ||
| 1234 : | <code> | ||
| 1235 : | ((IPropertyChangeListener) listeners[i]).propertyChange(event);</code><br> | ||
| 1236 : | <code> }</code><br> | ||
| 1237 : | <code></code><br> | ||
| 1238 : | <code> /**</code><br> | ||
| 1239 : | <code> * This method will automatically be | ||
| 1240 : | called | ||
| 1241 : | sxenos | 1.2 | when the component that uses this adapter is disposed.</code><br> |
| 1242 : | sxenos | 1.1 | <code> * It should clean up anything that has |
| 1243 : | been | ||
| 1244 : | allocated by the service.</code><br> | ||
| 1245 : | <code> */</code><br> | ||
| 1246 : | <code> public void dispose() {</code><br> | ||
| 1247 : | <code> prefs.removeListener(listener);</code><br> | ||
| 1248 : | <code> }</code><br> | ||
| 1249 : | <code></code><br> | ||
| 1250 : | <code> public String getString(String prefId) {</code><br> | ||
| 1251 : | <code> return prefs.getString(prefId);</code><br> | ||
| 1252 : | <code> }</code><br> | ||
| 1253 : | <code></code><br> | ||
| 1254 : | <code> public void setString(String prefId, String | ||
| 1255 : | value) {</code><br> | ||
| 1256 : | <code> prefs.setValue(prefId, value);</code><br> | ||
| 1257 : | <code> }</code><br> | ||
| 1258 : | <code>}</code><br> | ||
| 1259 : | <code></code></div> | ||
| 1260 : | <code></code><br> | ||
| 1261 : | Similarly, components may implement the IDisposable interface. A | ||
| 1262 : | sxenos | 1.2 | component that implements IDisposable will be disposed before the |
| 1263 : | objects it depends on, as shown by the following example:<br> | ||
| 1264 : | sxenos | 1.1 | <br> |
| 1265 : | <div style="margin-left: 40px;"><code>public LifecycleService | ||
| 1266 : | implements IDisposable {</code><br> | ||
| 1267 : | <code> public LifecycleService() {</code><br> | ||
| 1268 : | <code> | ||
| 1269 : | System.out.println("LifecycleService | ||
| 1270 : | created");</code><br> | ||
| 1271 : | <code> }</code><br> | ||
| 1272 : | <code></code><br> | ||
| 1273 : | <code> public void dispose() {</code><br> | ||
| 1274 : | <code> | ||
| 1275 : | System.out.println("LifecycleService | ||
| 1276 : | destroyed");</code><br> | ||
| 1277 : | <code> }</code><br> | ||
| 1278 : | <code>}</code><br> | ||
| 1279 : | <code></code><br> | ||
| 1280 : | <code>public LifecycleView implements IDisposable {</code><br> | ||
| 1281 : | <code> public LifecycleView(LifecycleService service) | ||
| 1282 : | {</code><br> | ||
| 1283 : | <code> System.out.println("LifecycleView | ||
| 1284 : | created");</code><br> | ||
| 1285 : | <code> }</code><br> | ||
| 1286 : | <code></code><br> | ||
| 1287 : | <code> public void dispose() {</code><br> | ||
| 1288 : | <code> System.out.println("LifecycleView | ||
| 1289 : | destroyed");</code><br> | ||
| 1290 : | <code> }</code><br> | ||
| 1291 : | <code>}</code><br> | ||
| 1292 : | <code></code><br> | ||
| 1293 : | </div> | ||
| 1294 : | sxenos | 1.2 | Finally, we could execute the following code to trace when the |
| 1295 : | LifecycleService is created and destroyed:<br> | ||
| 1296 : | sxenos | 1.1 | <div style="margin-left: 40px;"><br> |
| 1297 : | IMutableContainerFactory factory = | ||
| 1298 : | Components.getFactory().createDerivedFactory();<br> | ||
| 1299 : | factory.addComponentImplementation(LifecycleService.class);<br> | ||
| 1300 : | <br> | ||
| 1301 : | System.out.println("Creating a LifecycleView");<br> | ||
| 1302 : | <br> | ||
| 1303 : | IContainer viewComponent = factory.createComponent(LifecycleView.class);<br> | ||
| 1304 : | viewComponent.dispose();<br> | ||
| 1305 : | <br> | ||
| 1306 : | System.out.println("Creating two LifecycleViews");<br> | ||
| 1307 : | <br> | ||
| 1308 : | IContainer viewComponent2 = | ||
| 1309 : | factory.createComponent(LifecycleView.class);<br> | ||
| 1310 : | IContainer viewComponent3 = | ||
| 1311 : | factory.createComponent(LifecycleView.class);<br> | ||
| 1312 : | viewComponent3.dispose();<br> | ||
| 1313 : | viewComponent2.dispose();<br> | ||
| 1314 : | <br> | ||
| 1315 : | </div> | ||
| 1316 : | The code will generate the following output:<br> | ||
| 1317 : | <br> | ||
| 1318 : | <div style="margin-left: 40px;">Creating a LifecycleView<br> | ||
| 1319 : | LifecycleService created<br> | ||
| 1320 : | LifecycleView created<br> | ||
| 1321 : | LifecycleView destroyed<br> | ||
| 1322 : | LifecycleService destroyed<br> | ||
| 1323 : | <br> | ||
| 1324 : | Creating two LifecycleViews<br> | ||
| 1325 : | LifecycleService created<br> | ||
| 1326 : | LifecycleView created<br> | ||
| 1327 : | LifecycleService created<br> | ||
| 1328 : | LifecycleView created<br> | ||
| 1329 : | LifecycleView destroyed<br> | ||
| 1330 : | LifecycleService destroyed<br> | ||
| 1331 : | LifecycleView destroyed<br> | ||
| 1332 : | LifecycleService destroyed<br> | ||
| 1333 : | <br> | ||
| 1334 : | </div> | ||
| 1335 : | sxenos | 1.2 | <h2><a class="mozTocH2" name="mozTocId543223"></a>2.7 Dynamic Interfaces<br> |
| 1336 : | </h2> | ||
| 1337 : | Component interfaces are dynamic in the sense that they may be | ||
| 1338 : | registered or unregistered as their plugins are installed or | ||
| 1339 : | uninstalled. If a plugin providing component interface is uninstalled, | ||
| 1340 : | that interface will become unavailable and it will no longer be | ||
| 1341 : | possible to construct objects that depend on the interface.<br> | ||
| 1342 : | <br> | ||
| 1343 : | The objects instances themselves are not dynamic. Once an adapter is | ||
| 1344 : | created to supply an interface, it will exist for as long as the | ||
| 1345 : | component it was created for. | ||
| 1346 : | Even if the a plugin stops providing an interface, all instances of | ||
| 1347 : | that | ||
| 1348 : | interface will continue to exist until disposed. All interfaces used by | ||
| 1349 : | a | ||
| 1350 : | sxenos | 1.1 | component will be created before that component, so components do not |
| 1351 : | need to query for the existence of services or to wait for services | ||
| 1352 : | that will be created after-the-fact. <br> | ||
| 1353 : | sxenos | 1.2 | <h2><a class="mozTocH2" name="mozTocId886834"></a>2.8 Optional Interface<br> |
| 1354 : | </h2> | ||
| 1355 : | Sometimes a component does not require an interface but can make use of | ||
| 1356 : | it if it exists. This is normally not necessary for any interface | ||
| 1357 : | registered through the <code>org.eclipse.core.component.interface</code> | ||
| 1358 : | extension point since the default implementation will guarantee that | ||
| 1359 : | the interface always exists even if the parent context doesn't know | ||
| 1360 : | about it. However, this situation can occur if a parent and child are | ||
| 1361 : | communicating with an interface that wasn't originally intended for use | ||
| 1362 : | as a component interface, or if the child requires a variable set of | ||
| 1363 : | interfaces.<br> | ||
| 1364 : | <br> | ||
| 1365 : | Note: this pattern doesn't actually allow the component to be used in a | ||
| 1366 : | wider context, but it does allow the component to request interfaces | ||
| 1367 : | that aren't registered by the normal means.<br> | ||
| 1368 : | sxenos | 1.1 | <br> |
| 1369 : | sxenos | 1.2 | This can be done by taking an argument of type |
| 1370 : | sxenos | 1.1 | IAdaptable, like this:<br> |
| 1371 : | <br> | ||
| 1372 : | sxenos | 1.2 | <div style="margin-left: 40px;"><code>public interface INameService {<br> |
| 1373 : | public String getName();<br> | ||
| 1374 : | }<br> | ||
| 1375 : | <br> | ||
| 1376 : | <br> | ||
| 1377 : | /**<br> | ||
| 1378 : | sxenos | 1.1 | * A component with no dependencies, but which can optionally |
| 1379 : | accept an IMemento for initialization.<br> | ||
| 1380 : | */<br> | ||
| 1381 : | public class MyComponent {<br> | ||
| 1382 : | <br> | ||
| 1383 : | String myName = "default name";<br> | ||
| 1384 : | <br> | ||
| 1385 : | sxenos | 1.2 | public MyComponent(IAdaptable optionalInterfaces) {<br> |
| 1386 : | sxenos | 1.1 | <br> |
| 1387 : | sxenos | 1.2 | </code><code> // Check if an |
| 1388 : | INameService | ||
| 1389 : | sxenos | 1.1 | service exists.</code><br> |
| 1390 : | sxenos | 1.2 | <code> INameService nameService = |
| 1391 : | (INameService)optionalInterfaces.getAdapter(INameService.class);<br> | ||
| 1392 : | sxenos | 1.1 | <br> |
| 1393 : | if (memento != null) {<br> | ||
| 1394 : | sxenos | 1.2 | myName = |
| 1395 : | nameService.getName();<br> | ||
| 1396 : | sxenos | 1.1 | }<br> |
| 1397 : | <br> | ||
| 1398 : | System.out.println("created component | ||
| 1399 : | with name = " + myName);<br> | ||
| 1400 : | }<br> | ||
| 1401 : | }<br> | ||
| 1402 : | <br> | ||
| 1403 : | </code></div> | ||
| 1404 : | We could create the component like this:<br> | ||
| 1405 : | <br> | ||
| 1406 : | <div style="margin-left: 40px;"><code>// Create a memento with the name | ||
| 1407 : | "custom name"<br> | ||
| 1408 : | sxenos | 1.2 | INameService nameService = new INameService() {<br> |
| 1409 : | public String getName() {<br> | ||
| 1410 : | return "custom name";<br> | ||
| 1411 : | }<br> | ||
| 1412 : | }<br> | ||
| 1413 : | sxenos | 1.1 | <br> |
| 1414 : | // Create a factory which knows about our memento<br> | ||
| 1415 : | IMutableContainerFactory context = | ||
| 1416 : | Components.getFactory().createDerivedFactory();<br> | ||
| 1417 : | sxenos | 1.2 | context.addComponentInstance(nameService);<br> |
| 1418 : | sxenos | 1.1 | <br> |
| 1419 : | // Use the factory to instantiate MyComponent<br> | ||
| 1420 : | IContainer component = context.createContainer(MyComponent.class);<br> | ||
| 1421 : | // Will print the message "created component with name = custom name"<br> | ||
| 1422 : | component.dispose();</code><br> | ||
| 1423 : | <br> | ||
| 1424 : | </div> | ||
| 1425 : | sxenos | 1.2 | We can also create the component without the optional INameService:<br> |
| 1426 : | sxenos | 1.1 | <br> |
| 1427 : | <div style="margin-left: 40px;"><code>IContainer component = | ||
| 1428 : | Components.getFactory().createContainer(MyComponent.class);<br> | ||
| 1429 : | // Will print the message "create component with name = default name"<br> | ||
| 1430 : | component.dispose();</code><code></code><br> | ||
| 1431 : | </div> | ||
| 1432 : | <br> | ||
| 1433 : | <br> | ||
| 1434 : | sxenos | 1.2 | <span style="font-weight: bold;">WARNING: </span>This pattern should |
| 1435 : | be used with care since it adds special | ||
| 1436 : | cases to the component code. It is mainly inteded for advanced | ||
| 1437 : | situations where a component needs to request an interface that isn't | ||
| 1438 : | normally visible in its scope.<br> | ||
| 1439 : | <br> | ||
| 1440 : | The preferred method of dealing with missing interfaces is to provide a | ||
| 1441 : | default implementation, like this:<br> | ||
| 1442 : | <br> | ||
| 1443 : | <div style="margin-left: 40px;"><code><extension | ||
| 1444 : | point="</code><code>org.eclipse.core.component.interface</code><code>"></code><br> | ||
| 1445 : | <code> <interface | ||
| 1446 : | </code><br> | ||
| 1447 : | <code> | ||
| 1448 : | class="org.eclipse.ui.DefaultNameService"</code><br> | ||
| 1449 : | <code> </code><code>interface="</code><code>org.eclipse.ui.INameService</code><code>"</code><code>></code><br> | ||
| 1450 : | <code> </interface></code><br> | ||
| 1451 : | <code></extension><br> | ||
| 1452 : | <br> | ||
| 1453 : | <br> | ||
| 1454 : | /**<br> | ||
| 1455 : | * Default implementation of INameService that will be used if the | ||
| 1456 : | parent doesn't explicitly provide one<br> | ||
| 1457 : | */<br> | ||
| 1458 : | class NameService implements INameService {<br> | ||
| 1459 : | </code><code> public String getName() {<br> | ||
| 1460 : | return "default name";<br> | ||
| 1461 : | }<br> | ||
| 1462 : | </code><code>}<br> | ||
| 1463 : | <br> | ||
| 1464 : | </code><code>/**<br> | ||
| 1465 : | * A component with no dependencies, but which can optionally | ||
| 1466 : | accept an INameService for initialization.<br> | ||
| 1467 : | */<br> | ||
| 1468 : | public class MyComponent {<br> | ||
| 1469 : | <br> | ||
| 1470 : | String myName;<br> | ||
| 1471 : | <br> | ||
| 1472 : | public MyComponent(INameService nameService) {<br> | ||
| 1473 : | myName = nameService.getName();<br> | ||
| 1474 : | </code><code></code><code><br> | ||
| 1475 : | System.out.println("created component | ||
| 1476 : | with name = " + myName);<br> | ||
| 1477 : | }<br> | ||
| 1478 : | }</code><code><br> | ||
| 1479 : | </code></div> | ||
| 1480 : | <br> | ||
| 1481 : | With this approach, it is still possible to create a MyComponent with | ||
| 1482 : | or without an INameService, but the special-case code for handling | ||
| 1483 : | missing interfaces is written once inside the default implementation | ||
| 1484 : | rather than many times inside each component that uses | ||
| 1485 : | the interface.<br> | ||
| 1486 : | <h2><a class="mozTocH2" name="mozTocId578639"></a>2.8 Component Scopes</h2> | ||
| 1487 : | Scopes are a way to determine what interfaces can be used in the | ||
| 1488 : | constructor of a component. At some point, a programmer is going to ask | ||
| 1489 : | "What interfaces am I allowed to pass into the constructor of my view?" | ||
| 1490 : | Ultimately, this could be determined by examining the dependencies | ||
| 1491 : | between each service, but it is not reasonable to expect a programmer | ||
| 1492 : | to have this level of knowledge of the dependencies between services.<br> | ||
| 1493 : | <br> | ||
| 1494 : | Scopes limit the dependencies between services in order to make this | ||
| 1495 : | question easier to answer. For example, views are allowed to use any | ||
| 1496 : | service in the "/plugin/part/view" scope. This makes it easy to write a | ||
| 1497 : | PDE extension which displays the list of interfaces available to a | ||
| 1498 : | views. <br> | ||
| 1499 : | <br> | ||
| 1500 : | The only difference between scopes is how much context the components | ||
| 1501 : | are allowed to expect. For example, components in the <span | ||
| 1502 : | style="font-style: italic;">plugin</span> scope will be given a plugin | ||
| 1503 : | Bundle and components in the <span style="font-style: italic;">plugin/part</span> | ||
| 1504 : | scope need to be associated with a particular SWT Composite. Components | ||
| 1505 : | may depend on services in the same scope or in a | ||
| 1506 : | more general scope. It is still possible for Components to reference | ||
| 1507 : | services in a more specific scope, but they must do so as an optional | ||
| 1508 : | interface (as described in section 2.7).<span | ||
| 1509 : | style="font-style: italic;"></span><br> | ||
| 1510 : | <br> | ||
| 1511 : | Eclipse defines the following standard scopes.<br> | ||
| 1512 : | <br> | ||
| 1513 : | <table style="width: 100%; text-align: left;" border="1" cellpadding="2" | ||
| 1514 : | cellspacing="2"> | ||
| 1515 : | <tbody> | ||
| 1516 : | <tr> | ||
| 1517 : | <td style="vertical-align: top;">Scope path<br> | ||
| 1518 : | </td> | ||
| 1519 : | <td style="vertical-align: top;">Required context<br> | ||
| 1520 : | </td> | ||
| 1521 : | <td style="vertical-align: top;">Description<br> | ||
| 1522 : | </td> | ||
| 1523 : | </tr> | ||
| 1524 : | <tr> | ||
| 1525 : | <td style="vertical-align: top;">/<br> | ||
| 1526 : | </td> | ||
| 1527 : | <td style="vertical-align: top;">None<br> | ||
| 1528 : | </td> | ||
| 1529 : | <td style="vertical-align: top;">Any service that omits the <span | ||
| 1530 : | style="font-style: italic;">scope</span> attribute automatically | ||
| 1531 : | belongs to the global scope. Services in the global scope can be used | ||
| 1532 : | by any other component, but may only depend on other global services. | ||
| 1533 : | Global services are not given any context from the application.<br> | ||
| 1534 : | </td> | ||
| 1535 : | </tr> | ||
| 1536 : | <tr> | ||
| 1537 : | <td style="vertical-align: top;">/plugin<br> | ||
| 1538 : | </td> | ||
| 1539 : | <td style="vertical-align: top;">Bundle<br> | ||
| 1540 : | </td> | ||
| 1541 : | <td style="vertical-align: top;">Components in the /plugin scope | ||
| 1542 : | are created in the context of a plugin bundle. All executable | ||
| 1543 : | extensions (any component created using an extension point) belong to | ||
| 1544 : | the plugin scope. Components in this scope can reference their own | ||
| 1545 : | plugin Bundle in their constructor. Services can reference the Bundle | ||
| 1546 : | associated with the component that requested them.<br> | ||
| 1547 : | </td> | ||
| 1548 : | </tr> | ||
| 1549 : | <tr> | ||
| 1550 : | <td style="vertical-align: top;">/plugin/part<br> | ||
| 1551 : | </td> | ||
| 1552 : | <td style="vertical-align: top;">Bundle, Composite<br> | ||
| 1553 : | </td> | ||
| 1554 : | <td style="vertical-align: top;">Components in the /plugin/part | ||
| 1555 : | scope are associated with an SWT Composite. Components in this scope | ||
| 1556 : | are given their | ||
| 1557 : | own SWT Composite. They may change the layout on the given Composite, | ||
| 1558 : | but may not change its layout data. The composite will be managed as a | ||
| 1559 : | service, so the component does not need to dispose it. Components in | ||
| 1560 : | this scope may be used as editors, views, or both.<br> | ||
| 1561 : | </td> | ||
| 1562 : | </tr> | ||
| 1563 : | <tr> | ||
| 1564 : | <td style="vertical-align: top;">/plugin/part/view<br> | ||
| 1565 : | </td> | ||
| 1566 : | <td style="vertical-align: top;">Bundle,<br> | ||
| 1567 : | Composite,<br> | ||
| 1568 : | IWorkbenchPage<br> | ||
| 1569 : | </td> | ||
| 1570 : | <td style="vertical-align: top;">All views belong to this scope. | ||
| 1571 : | They are given the IConfigurationElement containing their extension | ||
| 1572 : | markup, and an IWorkbenchPage.<br> | ||
| 1573 : | </td> | ||
| 1574 : | </tr> | ||
| 1575 : | <tr> | ||
| 1576 : | <td style="vertical-align: top;">/plugin/part/editor<br> | ||
| 1577 : | </td> | ||
| 1578 : | <td style="vertical-align: top;">Bundle,<br> | ||
| 1579 : | Composite,<br> | ||
| 1580 : | IWorkbenchPage,<br> | ||
| 1581 : | IEditorInput<br> | ||
| 1582 : | </td> | ||
| 1583 : | <td style="vertical-align: top;">All editors belong to this | ||
| 1584 : | scope. They are given the IConfigurationElement containing their | ||
| 1585 : | extension markup, an IWorkbenchPage, and the IEditorInput containing | ||
| 1586 : | their input.<br> | ||
| 1587 : | </td> | ||
| 1588 : | </tr> | ||
| 1589 : | </tbody> | ||
| 1590 : | </table> | ||
| 1591 : | <br> | ||
| 1592 : | These scopes should be sufficient for most situations. However, some | ||
| 1593 : | plugins may wish to create new scopes in order to pass additional | ||
| 1594 : | context to the objects in an extension point they provided.<br> | ||
| 1595 : | <br> | ||
| 1596 : | Any plugin that defines its own scopes must include its fully qualified | ||
| 1597 : | plugin ID as part of the scope path. For example, if the plugin | ||
| 1598 : | org.eclipse.myplugin extends the /plugin/part/editor scope, the new | ||
| 1599 : | scope name would look like | ||
| 1600 : | /plugin/part/editor/org.eclipse.myplugin.scopename. All scope paths | ||
| 1601 : | that do not contain a period (.) are reserved by the framework.<br> | ||
| 1602 : | <br> | ||
| 1603 : | It is never a breaking change to move a service into a more general | ||
| 1604 : | scope. However, it is always a breaking change to move a service into a | ||
| 1605 : | more specific scope. A service interface may only exist in one scope.<br> | ||
| 1606 : | <h2><a class="mozTocH2" name="mozTocId762751"></a>2.9 Shared Adapters</h2> | ||
| 1607 : | Normally, a new instance of a default interface implementation is | ||
| 1608 : | created for each component that requests it. This allows the adapter to | ||
| 1609 : | keep some state associated with the component and to clean up resources | ||
| 1610 : | once they are no longer needed by the component. However, some | ||
| 1611 : | interface do not need any state or expose any API that could be used to | ||
| 1612 : | create a leak. For example, the IErrorContext interface in section 2.4 | ||
| 1613 : | could safely share one instance between all components in the same | ||
| 1614 : | plugin. In order to reduce memory consumption, the component API should | ||
| 1615 : | support the notion of a shared adapter. A shared adapter will be reused | ||
| 1616 : | in the broadest possible context permitted by its scope. For example, | ||
| 1617 : | an adapter for an interface in the global scope would become a | ||
| 1618 : | singleton, shared adapters in the plugin scope will have at most one | ||
| 1619 : | instance per plugin, and the shared flag will be ignored in the | ||
| 1620 : | /plugin/part scope.<br> | ||
| 1621 : | <br> | ||
| 1622 : | This is intended as a future optimization, and will not be included in | ||
| 1623 : | the initial version of the component framework<br> | ||
| 1624 : | <br> | ||
| 1625 : | <h1><a class="mozTocH1" name="mozTocId707022"></a>3.0 Views and Editors | ||
| 1626 : | as Components</h1> | ||
| 1627 : | This section describes how the workbench uses the component framework | ||
| 1628 : | to create editors and views.<br> | ||
| 1629 : | <br> | ||
| 1630 : | <h2><a class="mozTocH2" name="mozTocId125361"></a>3.1 Interfaces | ||
| 1631 : | offered by the workbench</h2> | ||
| 1632 : | Parent interfaces: (Interfaces given to a component as arguments in its | ||
| 1633 : | constructor)<br> | ||
| 1634 : | <br> | ||
| 1635 : | <table style="width: 100%; text-align: left;" border="1" cellpadding="2" | ||
| 1636 : | cellspacing="2"> | ||
| 1637 : | <tbody> | ||
| 1638 : | <tr> | ||
| 1639 : | <td style="vertical-align: top;">Scope<br> | ||
| 1640 : | </td> | ||
| 1641 : | <td style="vertical-align: top;">Interface<br> | ||
| 1642 : | </td> | ||
| 1643 : | <td style="vertical-align: top;">Description<br> | ||
| 1644 : | </td> | ||
| 1645 : | </tr> | ||
| 1646 : | <tr> | ||
| 1647 : | <td style="vertical-align: top;">/plugin<br> | ||
| 1648 : | </td> | ||
| 1649 : | <td style="vertical-align: top;">IErrorContext<br> | ||
| 1650 : | </td> | ||
| 1651 : | <td style="vertical-align: top;">Provides facilities for logging | ||
| 1652 : | exceptions<br> | ||
| 1653 : | </td> | ||
| 1654 : | </tr> | ||
| 1655 : | <tr> | ||
| 1656 : | <td style="vertical-align: top;">/plugin<br> | ||
| 1657 : | </td> | ||
| 1658 : | <td style="vertical-align: top;">ISwtResources<br> | ||
| 1659 : | </td> | ||
| 1660 : | <td style="vertical-align: top;">Provides facilities for | ||
| 1661 : | allocating SWT resources such as Fonts, Images, and Colors<br> | ||
| 1662 : | </td> | ||
| 1663 : | </tr> | ||
| 1664 : | <tr> | ||
| 1665 : | <td style="vertical-align: top;">/<br> | ||
| 1666 : | </td> | ||
| 1667 : | <td style="vertical-align: top;">INameable<br> | ||
| 1668 : | </td> | ||
| 1669 : | <td style="vertical-align: top;">Parts can use this service to | ||
| 1670 : | change their name, content description, title image, and tooltip. The | ||
| 1671 : | default implementation ignores all method calls.<br> | ||
| 1672 : | </td> | ||
| 1673 : | </tr> | ||
| 1674 : | <tr> | ||
| 1675 : | <td style="vertical-align: top;">/<br> | ||
| 1676 : | </td> | ||
| 1677 : | <td style="vertical-align: top;">IMemento<br> | ||
| 1678 : | </td> | ||
| 1679 : | <td style="vertical-align: top;">Parts will receive a memento | ||
| 1680 : | which they can use to load previously-saved state<br> | ||
| 1681 : | </td> | ||
| 1682 : | </tr> | ||
| 1683 : | <tr> | ||
| 1684 : | <td style="vertical-align: top;">/plugin<br> | ||
| 1685 : | </td> | ||
| 1686 : | <td style="vertical-align: top;">IPartFactory<br> | ||
| 1687 : | </td> | ||
| 1688 : | <td style="vertical-align: top;">Interface that can be used to | ||
| 1689 : | create child views, editors, and other UI parts<br> | ||
| 1690 : | </td> | ||
| 1691 : | </tr> | ||
| 1692 : | <tr> | ||
| 1693 : | <td style="vertical-align: top;">/plugin/part<br> | ||
| 1694 : | </td> | ||
| 1695 : | <td style="vertical-align: top;">IActionBars2<br> | ||
| 1696 : | </td> | ||
| 1697 : | <td style="vertical-align: top;">Interface used to add to the | ||
| 1698 : | toolbar, cool bar, etc. Currently exposed on the view site.<br> | ||
| 1699 : | </td> | ||
| 1700 : | </tr> | ||
| 1701 : | <tr> | ||
| 1702 : | <td style="vertical-align: top;"><br> | ||
| 1703 : | </td> | ||
| 1704 : | <td style="vertical-align: top;"><br> | ||
| 1705 : | </td> | ||
| 1706 : | <td style="vertical-align: top;"><br> | ||
| 1707 : | </td> | ||
| 1708 : | </tr> | ||
| 1709 : | </tbody> | ||
| 1710 : | </table> | ||
| 1711 : | <br> | ||
| 1712 : | Child interfaces: (Services that a component offers to its parent - | ||
| 1713 : | either by implementing directly or as an adapter)<br> | ||
| 1714 : | <br> | ||
| 1715 : | <table style="text-align: left; width: 1550px; height: 88px;" border="1" | ||
| 1716 : | cellpadding="2" cellspacing="2"> | ||
| 1717 : | <tbody> | ||
| 1718 : | <tr> | ||
| 1719 : | <td style="vertical-align: top;">Scope<br> | ||
| 1720 : | </td> | ||
| 1721 : | <td style="vertical-align: top;">Interface<br> | ||
| 1722 : | </td> | ||
| 1723 : | <td style="vertical-align: top;">Description<br> | ||
| 1724 : | </td> | ||
| 1725 : | </tr> | ||
| 1726 : | <tr> | ||
| 1727 : | <td style="vertical-align: top;">/plugin/part<br> | ||
| 1728 : | </td> | ||
| 1729 : | <td style="vertical-align: top;">IFocusable<br> | ||
| 1730 : | </td> | ||
| 1731 : | <td style="vertical-align: top;">Parts can implement this service | ||
| 1732 : | to allow their parent<br> | ||
| 1733 : | </td> | ||
| 1734 : | </tr> | ||
| 1735 : | <tr> | ||
| 1736 : | <td style="vertical-align: top;">/<br> | ||
| 1737 : | </td> | ||
| 1738 : | <td style="vertical-align: top;">IMultiPart<br> | ||
| 1739 : | </td> | ||
| 1740 : | <td style="vertical-align: top;">Parts should implement this if | ||
| 1741 : | they contain other parts and have the notion of an "active" child. | ||
| 1742 : | Other services can use this service if their default implementation | ||
| 1743 : | should redirect to the active child.<br> | ||
| 1744 : | </td> | ||
| 1745 : | </tr> | ||
| 1746 : | <tr> | ||
| 1747 : | <td style="vertical-align: top;">/<br> | ||
| 1748 : | </td> | ||
| 1749 : | <td style="vertical-align: top;">IPersistable<br> | ||
| 1750 : | </td> | ||
| 1751 : | <td style="vertical-align: top;">Parts may implement this | ||
| 1752 : | interface if they wish to save their state between sessions.<br> | ||
| 1753 : | </td> | ||
| 1754 : | </tr> | ||
| 1755 : | <tr> | ||
| 1756 : | <td style="vertical-align: top;"><br> | ||
| 1757 : | </td> | ||
| 1758 : | <td style="vertical-align: top;"><br> | ||
| 1759 : | </td> | ||
| 1760 : | <td style="vertical-align: top;"><br> | ||
| 1761 : | </td> | ||
| 1762 : | </tr> | ||
| 1763 : | </tbody> | ||
| 1764 : | </table> | ||
| 1765 : | <br> | ||
| 1766 : | <br> | ||
| 1767 : | <h3><a class="mozTocH2" name="mozTocId163729"></a>3.1.1 IMultiPart: | ||
| 1768 : | Redirecting | ||
| 1769 : | adapters from the active child<br> | ||
| 1770 : | </h3> | ||
| 1771 : | sxenos | 1.1 | In UI code, it is common to create aggregate components that have an |
| 1772 : | sxenos | 1.2 | "active" child. This section suggests a general pattern for redirecting |
| 1773 : | components in this way. Many of the adapters that the aggregate offers | ||
| 1774 : | its | ||
| 1775 : | parent will redirect their implementation to the active child. The | ||
| 1776 : | aggregate itself will know | ||
| 1777 : | sxenos | 1.1 | how to select its active child, but it may not know about all the |
| 1778 : | sxenos | 1.2 | interfaces that need to be redirected in this manner. The default |
| 1779 : | implementation of an interface determines how it should be redirected.<br> | ||
| 1780 : | sxenos | 1.1 | <br> |
| 1781 : | sxenos | 1.2 | This can be shown using a concrete example. Workbench parts |
| 1782 : | sxenos | 1.1 | offer an IFocusable adapter to their parent that allows their parent to |
| 1783 : | give them focus. <br> | ||
| 1784 : | <br> | ||
| 1785 : | <code></code> | ||
| 1786 : | <div style="margin-left: 40px;"><code>/**<br> | ||
| 1787 : | * Parts can implement this interface if they wish to overload the | ||
| 1788 : | default<br> | ||
| 1789 : | * setFocus behavior.<br> | ||
| 1790 : | */<br> | ||
| 1791 : | public interface IFocusable {<br> | ||
| 1792 : | /**<br> | ||
| 1793 : | * Gives focus to the part<br> | ||
| 1794 : | */<br> | ||
| 1795 : | public void setFocus();<br> | ||
| 1796 : | }</code><br> | ||
| 1797 : | <br> | ||
| 1798 : | </div> | ||
| 1799 : | This interface should be multiplexed by default. For example, if we | ||
| 1800 : | call setFocus() on a multi-page editor that doesn't explicitly | ||
| 1801 : | implement the IFocusable interface, it should redirect focus to its | ||
| 1802 : | active child. If the part doesn't have an active child, focus should go | ||
| 1803 : | directly to the part's main control. To supply this default behavior, | ||
| 1804 : | sxenos | 1.2 | we provide a default implementation of IFocusable:<br> |
| 1805 : | sxenos | 1.1 | <br> |
| 1806 : | <code></code> | ||
| 1807 : | <div style="margin-left: 40px;"><code>/**<br> | ||
| 1808 : | * Default implementation of the IFocusable service. If a part | ||
| 1809 : | doesn't explicitly<br> | ||
| 1810 : | * provide an adapter for IFocusable, this implementation will be | ||
| 1811 : | used.<br> | ||
| 1812 : | */<br> | ||
| 1813 : | public class DefaultFocusable implements IFocusable {<br> | ||
| 1814 : | <br> | ||
| 1815 : | private Composite control;<br> | ||
| 1816 : | sxenos | 1.2 | private IMultiPart activePart;<br> |
| 1817 : | sxenos | 1.1 | <br> |
| 1818 : | /**<br> | ||
| 1819 : | * Creates the default implementation of | ||
| 1820 : | IFocusable, given the main control<br> | ||
| 1821 : | sxenos | 1.2 | * of the part and an IMultiPart that can be |
| 1822 : | sxenos | 1.1 | queried for the active child.<br> |
| 1823 : | * <br> | ||
| 1824 : | * @param toGiveFocus main control of the pane<br> | ||
| 1825 : | * @param activePartProvider multiplexer that | ||
| 1826 : | returns the active part<br> | ||
| 1827 : | */<br> | ||
| 1828 : | public DefaultFocusable(Composite toGiveFocus, | ||
| 1829 : | sxenos | 1.2 | IMultiPart activePartProvider) {<br> |
| 1830 : | sxenos | 1.1 | control = toGiveFocus;<br> |
| 1831 : | activePart = activePartProvider;<br> | ||
| 1832 : | }<br> | ||
| 1833 : | <br> | ||
| 1834 : | /**<br> | ||
| 1835 : | * First, try to give focus to the active | ||
| 1836 : | child. If there is no active child, give focus to<br> | ||
| 1837 : | * the main control.<br> | ||
| 1838 : | */<br> | ||
| 1839 : | public void setFocus() {<br> | ||
| 1840 : | // If this part has the notion of | ||
| 1841 : | an active child, give that child focus<br> | ||
| 1842 : | Object current = | ||
| 1843 : | activePart.getCurrent();<br> | ||
| 1844 : | if (current != null) {<br> | ||
| 1845 : | IFocusable | ||
| 1846 : | focusable = (IFocusable)Components.getAdapter(current, | ||
| 1847 : | IFocusable.class);<br> | ||
| 1848 : | if (focusable | ||
| 1849 : | != null) {<br> | ||
| 1850 : | | ||
| 1851 : | focusable.setFocus();<br> | ||
| 1852 : | | ||
| 1853 : | return;<br> | ||
| 1854 : | }<br> | ||
| 1855 : | }<br> | ||
| 1856 : | <br> | ||
| 1857 : | // If the part has no children, | ||
| 1858 : | then give focus to the part itself. | ||
| 1859 : | <br> | ||
| 1860 : | control.setFocus();<br> | ||
| 1861 : | }<br> | ||
| 1862 : | }</code><br> | ||
| 1863 : | <br> | ||
| 1864 : | </div> | ||
| 1865 : | sxenos | 1.2 | Here is the XML markup to register the interface:<br> |
| 1866 : | sxenos | 1.1 | <br> |
| 1867 : | <div style="margin-left: 40px;"><code><service<br> | ||
| 1868 : | | ||
| 1869 : | interface="org.eclipse.ui.workbench.services.IFocusable"<br> | ||
| 1870 : | | ||
| 1871 : | sxenos | 1.2 | class="org.eclipse.ui.internal.part.serviceimplementation.DefaultFocusable<br> |
| 1872 : | childadapter="true"<br> | ||
| 1873 : | scope="plugin/part"/></code><code></code><br> | ||
| 1874 : | sxenos | 1.1 | </div> |
| 1875 : | <br> | ||
| 1876 : | sxenos | 1.2 | All of the interfaces we have looked at so far have been offered by a |
| 1877 : | parent for use in a child's constructor. In this case, the interface is | ||
| 1878 : | sxenos | 1.1 | offered as an adapter on the child to be used by the parent. In both |
| 1879 : | sxenos | 1.2 | cases, we support the notion of a default implementation that is |
| 1880 : | declared through XML. Any service that should be redirected based on | ||
| 1881 : | the active child will take IMultiPart in its constructor. Here is an | ||
| 1882 : | example part that supports | ||
| 1883 : | sxenos | 1.1 | multiplexing:<br> |
| 1884 : | <code><br> | ||
| 1885 : | </code> | ||
| 1886 : | <div style="margin-left: 40px;"><code>/**<br> | ||
| 1887 : | * Part that explicitly implements IFocusable.<br> | ||
| 1888 : | */<br> | ||
| 1889 : | public class Page implements IFocusable {<br> | ||
| 1890 : | Text textField;<br> | ||
| 1891 : | <br> | ||
| 1892 : | public Page(Composite control) {<br> | ||
| 1893 : | textField = new Text(control, SWT.NONE);<br> | ||
| 1894 : | }<br> | ||
| 1895 : | <br> | ||
| 1896 : | public void setFocus() {<br> | ||
| 1897 : | textField.setFocus();<br> | ||
| 1898 : | }<br> | ||
| 1899 : | }<br> | ||
| 1900 : | <br> | ||
| 1901 : | /**<br> | ||
| 1902 : | * Non-multiplexing part that relies on the DefaultFocusable | ||
| 1903 : | service<br> | ||
| 1904 : | * to give focus to its composite.<br> | ||
| 1905 : | */<br> | ||
| 1906 : | public class Page2 {<br> | ||
| 1907 : | Text textField;<br> | ||
| 1908 : | <br> | ||
| 1909 : | public Page(Composite control) {<br> | ||
| 1910 : | textField = new Text(control, SWT.NONE);<br> | ||
| 1911 : | }<br> | ||
| 1912 : | }<br> | ||
| 1913 : | <br> | ||
| 1914 : | /**<br> | ||
| 1915 : | * Multiplexing part that contains a Page, a Page2, and a checkbox.<br> | ||
| 1916 : | * When the checkbox is selected, the first page will be active. | ||
| 1917 : | When the<br> | ||
| 1918 : | * checkbox is deselected, the second page will be active. Calling | ||
| 1919 : | setFocus<br> | ||
| 1920 : | * on the MultiplexingView will always give focus to the active | ||
| 1921 : | page.<br> | ||
| 1922 : | */<br> | ||
| 1923 : | public class MultiplexingView implements IDisposable, IAdaptable {<br> | ||
| 1924 : | private IContainer page1;<br> | ||
| 1925 : | private IContainer page2;<br> | ||
| 1926 : | <br> | ||
| 1927 : | sxenos | 1.2 | // Helper class that implements the IMultiPart |
| 1928 : | sxenos | 1.1 | interface<br> |
| 1929 : | private Multiplexer multiplexer = new Multiplexer();<br> | ||
| 1930 : | private Button checkBox;<br> | ||
| 1931 : | <br> | ||
| 1932 : | public MultiplexingView(Composite myControl) {<br> | ||
| 1933 : | myControl.setLayout(new RowLayout());<br> | ||
| 1934 : | <br> | ||
| 1935 : | checkBox = new Button(myControl, | ||
| 1936 : | SWT.CHECK);<br> | ||
| 1937 : | checkBox.addSelectionListener(new | ||
| 1938 : | SelectionAdapter() {<br> | ||
| 1939 : | public void | ||
| 1940 : | widgetSelected(SelectionEvent e) {<br> | ||
| 1941 : | | ||
| 1942 : | updateActivePart();<br> | ||
| 1943 : | }<br> | ||
| 1944 : | });<br> | ||
| 1945 : | <br> | ||
| 1946 : | checkBox.setText("Activate part 1");<br> | ||
| 1947 : | <br> | ||
| 1948 : | // Create the child controls<br> | ||
| 1949 : | IMutableComponentFactory childFactory = | ||
| 1950 : | Components.getFactory().createDerivedFactory();<br> | ||
| 1951 : | <br> | ||
| 1952 : | // Use the CompositeAdapter class we | ||
| 1953 : | created earlier in order to provide each page<br> | ||
| 1954 : | // with its own control.<br> | ||
| 1955 : | childFactory.addComponentInstance(new | ||
| 1956 : | CompositeAdapter(myControl));<br> | ||
| 1957 : | <br> | ||
| 1958 : | page1 = | ||
| 1959 : | childFactory.createContainer(Page.class);<br> | ||
| 1960 : | page2 = | ||
| 1961 : | childFactory.createContainer(Page2.class);<br> | ||
| 1962 : | <br> | ||
| 1963 : | // Activate the initial part<br> | ||
| 1964 : | </code><code> updateActivePart();</code><br> | ||
| 1965 : | <code> }<br> | ||
| 1966 : | <br> | ||
| 1967 : | private final void updateActivePart() {<br> | ||
| 1968 : | </code><code> if | ||
| 1969 : | (checkBox.getSelection()) {<br> | ||
| 1970 : | | ||
| 1971 : | multiplexer.setCurrent(page1);<br> | ||
| 1972 : | } else {<br> | ||
| 1973 : | | ||
| 1974 : | multiplexer.setCurrent(page2);<br> | ||
| 1975 : | }</code><br> | ||
| 1976 : | <code> }<br> | ||
| 1977 : | <br> | ||
| 1978 : | /**<br> | ||
| 1979 : | * In order to indicate that this is a | ||
| 1980 : | multiplexing part, we need to implement<br> | ||
| 1981 : | * the IMultiplexer adapter. We simply redirect | ||
| 1982 : | to the multiplexer object.<br> | ||
| 1983 : | */<br> | ||
| 1984 : | public Object getAdapter(Class adapterType) {<br> | ||
| 1985 : | if (adapterType == IMultiplexer.class) {<br> | ||
| 1986 : | return multiplexer;<br> | ||
| 1987 : | }<br> | ||
| 1988 : | }<br> | ||
| 1989 : | <br> | ||
| 1990 : | /**<br> | ||
| 1991 : | * Release any resources allocated in the | ||
| 1992 : | constructor<br> | ||
| 1993 : | */<br> | ||
| 1994 : | public void dispose() {<br> | ||
| 1995 : | page1.dispose();<br> | ||
| 1996 : | page2.dispose();<br> | ||
| 1997 : | }<br> | ||
| 1998 : | }</code><br> | ||
| 1999 : | </div> | ||
| 2000 : | <br> | ||
| 2001 : | Notice that MultiplexingView itself doesn't know about the IFocusable | ||
| 2002 : | interface, however if we were to create a MultiplexingView and ask its | ||
| 2003 : | container for an IFocusable interface, we would get an interface that | ||
| 2004 : | behaves as expected. <br> | ||
| 2005 : | <br> | ||
| 2006 : | <div style="margin-left: 40px;"><code></code><code>// Stupid example | ||
| 2007 : | that creates a MultiplexingView, gives it focus, then destroys it<br> | ||
| 2008 : | <br> | ||
| 2009 : | // Create a MultiplexingView</code><br> | ||
| 2010 : | <code>IMutableComponentFactory childFactory = | ||
| 2011 : | Components.getFactory().createDerivedFactory();<br> | ||
| 2012 : | </code><code>childFactory.addComponentInstance(new | ||
| 2013 : | CompositeAdapter(myControl));<br> | ||
| 2014 : | <br> | ||
| 2015 : | IContainer viewContainer = | ||
| 2016 : | childFactory.createContainer(MultiplexingView.class);<br> | ||
| 2017 : | <br> | ||
| 2018 : | // Give focus to the active part<br> | ||
| 2019 : | IFocusable focusable = viewContainer.getAdapter(IFocusable.class);<br> | ||
| 2020 : | focusable.setFocus();<br> | ||
| 2021 : | </code><code></code><code><br> | ||
| 2022 : | // Destroy the view<br> | ||
| 2023 : | viewContainer.dispose();<br> | ||
| 2024 : | </code><code></code></div> | ||
| 2025 : | <code> <br> | ||
| 2026 : | sxenos | 1.2 | </code>This example also shows how the same default implementation can |
| 2027 : | work for both | ||
| 2028 : | sxenos | 1.1 | multiplexing and non-multiplexing parts. The Page2 class doesn't |
| 2029 : | sxenos | 1.2 | implement IFocusable or IMultiPart, but we are still able to |
| 2030 : | sxenos | 1.1 | ask for an IFocusable interface and use it to give focus to the part.<br> |
| 2031 : | sxenos | 1.2 | <span style="font-weight: bold;"><br> |
| 2032 : | </span><span style="font-weight: bold;"></span> | ||
| 2033 : | <h3><a class="mozTocH3" name="mozTocId483376"></a>3.1.2 INameable: | ||
| 2034 : | Changing the name of a part</h3> | ||
| 2035 : | In order to change their name, existing parts need to implement a set | ||
| 2036 : | of get methods, maintain a listener list, and send notifications when | ||
| 2037 : | their name changes. Component-based parts will change their name by | ||
| 2038 : | using an INameable interface provided by their parent. INameable looks | ||
| 2039 : | like this:<br> | ||
| 2040 : | <br> | ||
| 2041 : | <div style="margin-left: 40px;"><code>public interface INameable {<br> | ||
| 2042 : | public void setName(String newName);<br> | ||
| 2043 : | public void setContentDescription(String | ||
| 2044 : | contentDescription);<br> | ||
| 2045 : | public void setImage(Image theImage);<br> | ||
| 2046 : | public void setTooltip(String toolTip);<br> | ||
| 2047 : | }<br> | ||
| 2048 : | <br> | ||
| 2049 : | </code></div> | ||
| 2050 : | For example, a view could set its name like this:<br> | ||
| 2051 : | <br> | ||
| 2052 : | <div style="margin-left: 40px;"><code>public class MyView {<br> | ||
| 2053 : | public MyView(Composite parent, INameable name) {<br> | ||
| 2054 : | name.setName("Some tab text");<br> | ||
| 2055 : | }<br> | ||
| 2056 : | }<br> | ||
| 2057 : | <br> | ||
| 2058 : | </code></div> | ||
| 2059 : | If a parent cares about the names of its children, it should provide an | ||
| 2060 : | implementation of INameable that reacts when the child changes its | ||
| 2061 : | name. Otherwise, the child will be given a default implementation of | ||
| 2062 : | INameable that ignores all method calls. This eliminates the need to | ||
| 2063 : | attach listeners to views.<br> | ||
| 2064 : | <h3><a class="mozTocH3" name="mozTocId969580"></a>3.1.3 IPartFactory: | ||
| 2065 : | Creating child parts<br> | ||
| 2066 : | </h3> | ||
| 2067 : | Any part that wants to create other nested parts would use an | ||
| 2068 : | IPartFactory interface.<br> | ||
| 2069 : | <br> | ||
| 2070 : | <div style="margin-left: 40px;"><code>public interface IPartFactory {<br> | ||
| 2071 : | public IContainer createView(String viewId, | ||
| 2072 : | Composite parentComposite, IWorkbenchPage page, IContainerFactory | ||
| 2073 : | services);<br> | ||
| 2074 : | public IContainer createEditor(String editorId, | ||
| 2075 : | Composite parentComposite, IWorkbenchPage page, </code><code>IEditorInput | ||
| 2076 : | input, </code><code>IContainerFactory services);<br> | ||
| 2077 : | public IContainer createPart(String partId, | ||
| 2078 : | Composite parentComposite, IContainerFactory services);<br> | ||
| 2079 : | }</code><br> | ||
| 2080 : | sxenos | 1.1 | <br> |
| 2081 : | <br> | ||
| 2082 : | sxenos | 1.2 | </div> |
| 2083 : | <div style="margin-left: 40px;"><code></code><code></code></div> | ||
| 2084 : | <span style="font-weight: bold;"></span> | ||
| 2085 : | sxenos | 1.1 | </body> |
| 2086 : | </html> |
| help@eclipse.org | ViewVC Help |
| Powered by ViewVC 1.0.3 |
