From Zero to Ship: Eclipse RCP and Plug-in Developer Toolkit

Eclipse RCP/Plug-in Development: Best Practices and PatternsEclipse Rich Client Platform (RCP) and plug-in development remain powerful ways to build modular, extensible desktop applications on top of a mature, well-documented platform. This article collects practical best practices and design patterns gathered from real-world projects to help you build maintainable, testable, and extensible RCP applications and plug-ins. It covers architecture, project structure, UI patterns, performance, testing, packaging, and maintenance.


1. Understand the RCP fundamentals

Eclipse RCP is more than a UI toolkit — it’s a modular application framework built on OSGi, an extension registry (the extension point model), SWT (the widget toolkit), JFace (abstractions on SWT), and the Workbench (windowing, perspectives, views, editors). Before deep design decisions:

  • Know the difference between OSGi bundles and Eclipse plug-ins.
  • Understand extension points vs. API-based extension (services).
  • Learn the lifecycle: bundle activation, UI thread (SWT UI thread / Display), and Workbench lifecycle.
  • Grasp how the RCP Application, Product, and WorkbenchAdvisor fit together.

2. Project and module structure

Design your codebase to maximize modularity, reduce coupling, and speed up builds.

  • Use feature-based grouping for releases and provisioning; keep related plug-ins in a feature.
  • Coarse-grain modules: split by responsibility (UI, core/domain, persistence, services, tests).
  • Avoid monolithic plug-ins. Prefer multiple small plug-ins with well-defined API plug-ins that expose only stable interfaces.
  • Keep UI code separated from business logic. Use an application layer (service interfaces) so headless uses and unit tests are easier.
  • For larger teams, adopt a naming convention reflecting company/project (com.example.product.module).

Example layout:

  • com.example.app.product (product definition)
  • com.example.app.ui (views, editors, advisors)
  • com.example.app.core.api (interfaces)
  • com.example.app.core.impl (implementations)
  • com.example.app.persistence (DB access)
  • com.example.app.tests (integration/unit tests)

3. API and extension-point design

Well-designed extension points and APIs are the heart of extensible plug-ins.

  • Prefer API-first: create small API plug-ins that contain only interfaces, constants, and lightweight DTOs. Keep implementation in separate plug-ins.
  • Keep API stable. Once published, changing method signatures breaks third-party plug-ins.
  • Use extension points when third parties need to declaratively register behavior (e.g., new views, actions, validators). Document them clearly.
  • Provide programmatic service registries for dynamic or complex interactions—OSGi services are preferable when runtime dynamics matter.
  • Avoid exposing internal classes in your API. Use wrapper facades if necessary.

4. Use OSGi services and Declarative Services (DS)

OSGi services and Declarative Services (annotations like @Component, @Reference) offer flexible, decoupled runtime wiring.

  • Prefer DS (SCR) or OSGi Declarative Services over manual bundle activator wiring. DS manages lifecycle and dependencies.
  • Design services around interfaces, not implementations.
  • Handle optional/multiple service references gracefully (use cardinalities).
  • Use ServiceFactory or prototype scope for stateful service instances per consumer when appropriate.
  • Avoid static singletons; prefer OSGi service lookup or DS-injected fields.

5. UI architecture and patterns

A clean separation between UI and logic makes the application testable and maintainable.

  • Use MVC/MVP/MVVM patterns for views and editors:
    • MVC: model holds data, controller coordinates, view renders.
    • MVP: presenter drives view; useful when SWT/JFace view logic is complex.
    • MVVM: use data binding (EMF databinding or JFace Data Binding) to minimize glue code.
  • Prefer JFace viewers (TableViewer, TreeViewer, ListViewer) and content/label providers over manual widget management.
  • Keep heavy computation off the SWT UI thread — use Job API, Executors, or background threads and update UI via Display.asyncExec().
  • Use IProgressService and Jobs for long-running tasks and show progress to users.
  • Keep UI responsive: minimize layout and control creation in UI thread when possible; lazily create costly components.
  • Use Commands/Handlers and the Command Framework for actions rather than ad-hoc listeners. This enables keybindings, menu contributions, and command reuse.
  • Use contexts, activities, and filtering for progressive disclosure (hide advanced features until needed).

6. Preferences, settings, and persistence

Design a flexible and version-tolerant persistence strategy.

  • Use the Eclipse Preferences API for simple user and instance preferences (IEclipsePreferences). Keep defaults in preference initializer.
  • For complex data, use EMF, JPA, or a light embedded database (H2, SQLite). Wrap persistence behind interfaces to allow swapping implementations.
  • Migrate preferences and persisted data carefully between versions; implement migration code and keep transformation logic near persistence layer.
  • Avoid storing UI-only or transient state in long-term storage.

7. Resource management and memory

Desktop apps must manage UI and native resources carefully.

  • Dispose SWT resources (Colors, Fonts, Images, Cursors) when no longer needed. Use ImageDescriptor and shared ImageRegistry to centralize images and avoid leaks.
  • Use lightweight images (SVG where supported via Eclipse e4 or conversion) and scale appropriately.
  • Track and close listeners/observers to avoid retained references.
  • Use weak listeners or explicit removal patterns for global event listeners.
  • Profile memory and leak sources with tools (Eclipse Memory Analyzer, MAT).

8. Performance and startup optimization

Users judge applications by how fast they start and respond.

  • Minimize work in plugin activators and early startup. Delay initialization until needed (lazy start).
  • Mark plug-ins as lazy-start or avoid setting org.eclipse.core.runtime.compatibility. You can also use “Start Level” to control when bundles are started.
  • Reduce the number of contributed UI elements that load at startup—defer contributions via org.eclipse.ui.menus and parameterized commands.
  • Keep the product’s list of features and plug-ins lean; remove unused dependencies.
  • Use p2 advice: create installable units that allow optional features and decrease base footprint.
  • Profile startup with the Eclipse startup tracer and analyze plugin activation times.

9. Testing strategy

A strong testing strategy improves reliability and simplifies maintenance.

  • Unit tests: keep logic in non-UI modules for easy unit testing. Use JUnit and mock OSGi/Services where needed.
  • Integration tests: use Eclipse PDE JUnit tests or Tycho for integration testing of plug-ins. Run headless when possible.
  • UI tests: use SWTBot or Jubula for functional UI testing. Keep UI tests focused on flows and critical user scenarios; they are slower and more brittle.
  • CI pipeline: run unit tests, integration tests, static analysis, and packaging in CI (Jenkins, GitHub Actions, GitLab CI). Use Tycho or Buckminster for reproducible builds.
  • Test OSGi dynamics: test service registration/unregistration and component lifecycles under simulated conditions.

10. Packaging, updates, and provisioning

Choose packaging and update strategies that match your distribution model.

  • For Eclipse update sites, use p2 repositories. Structure features and installable units to make optional components truly optional.
  • Use Tycho Maven plugins for building Eclipse artifacts and p2 repositories in CI.
  • For standalone RCP apps, build products and include only necessary plug-ins. Use branding (product, about, icons) and provide native launchers.
  • Consider using Oomph for development environment setup and provisioning for developers.
  • Support delta-updates and provide clear versioning (semantic versioning helps). Use touchpoints and p2.inf to customize install behavior.

11. Versioning and compatibility

Predictable versioning reduces upgrade pain for users and integrators.

  • Use semantic versioning where possible for APIs. Keep major version bumps for breaking changes.
  • For plug-ins, follow Eclipse versioning guidelines: major.minor.micro.qualifier and properly update exported-package compatibility.
  • Maintain backward compatibility for public APIs. Deprecate before removal and provide migration guides.
  • Keep tight control of third-party dependencies and record license info.

12. Logging, diagnostics, and error handling

Good observability is vital for debugging and support.

  • Use the Eclipse ILog API or a tested logging framework adapted to Eclipse (e.g., SLF4J routed to Eclipse log).
  • Log useful context (user actions, plugin IDs, timestamps) but avoid PII.
  • Provide an easy way for users to collect and send logs (log collector or Diagnostics view).
  • Handle exceptions at boundaries and present user-friendly messages; avoid stack traces in UI—offer a “Details” button that shows technical info.
  • Integrate health checks and optional telemetry (respect user privacy and opt-in policies).

13. Security and sandboxing

Even desktop apps have security considerations.

  • Validate input, especially if you execute scripts or load external content.
  • Limit use of reflection and classloaders in ways that expose internals.
  • Be careful with native code and JNI; prefer pure Java where possible.
  • For plug-ins that execute third-party code, run them in restricted contexts and document capabilities.

14. Developer ergonomics and tooling

Make life easier for contributors and maintainers.

  • Provide a developer setup guide: workspace preferences, Oomph setup, build instructions, test commands.
  • Use code style, linters, and formatters (Save Actions, Checkstyle, SpotBugs).
  • Set up Git branching strategy and PR templates that require tests and changelog entries.
  • Provide example plug-ins and integration tests for new extension points so third-party authors can get started quickly.
  • Use continuous integration and nightly builds to catch regressions early.

15. Common design patterns and anti-patterns

Patterns

  • Extension Point Pattern: define extension points for declarative plug-in contributions.
  • Service Locator / OSGi Service Pattern: use service interfaces and dynamic lookup via OSGi.
  • Command Pattern: use commands/handlers for actions and undo/redo where applicable.
  • Adapter Pattern: implement adapters for legacy APIs to provide a unified interface to clients.
  • MVC/MVP/MVVM: separate concerns between UI and business logic.
  • Singleton via OSGi service: instead of static singletons, register a service as a single component.

Anti-patterns

  • Big-ball-of-mud plug-in that mixes UI, persistence, and domain logic.
  • Heavy initialization on startup or in activator.
  • Leaking SWT resources and failing to dispose images/colors/fonts.
  • Exposing internal classes in API plug-ins.
  • Tight coupling to Eclipse IDE specifics in an RCP app (makes headless usage and tests harder).

16. Migration and modernization (e4, Java versions)

Eclipse has evolved; plan migrations carefully.

  • Consider moving to e4 model if starting new: e4 provides an application model, dependency injection, CSS styling, and modernized APIs. However, evaluate the migration effort from 3.x RCP.
  • Keep Java compatibility and update target JRE gradually. Test third-party bundles for compatibility when increasing Java version.
  • For modularity, evaluate JPMS only if it brings benefit; OSGi and JPMS interactions can be complex.

17. Real-world checklist

  • Separate API/impl plug-ins.
  • Prefer Declarative Services over Activator wiring.
  • Use Commands/Handlers; avoid ad-hoc action wiring.
  • Centralize images in ImageRegistry and dispose resources.
  • Run unit, integration, and UI tests in CI.
  • Keep bundles lazy-start and delay heavy initialization.
  • Provide clear extension point docs and example contributions.
  • Use Tycho for builds and p2 for updates.
  • Implement migration paths for preferences and persisted data.
  • Profile startup and memory periodically.

18. Example quick patterns (code snippets)

  • Use Job to run background work:
Job job = new Job("Background task") {     @Override     protected IStatus run(IProgressMonitor monitor) {         // do work off UI thread         Display.getDefault().asyncExec(() -> {             // update UI         });         return Status.OK_STATUS;     } }; job.schedule(); 
  • Simple Declarative Service component:
@Component(service = MyService.class) public class MyServiceImpl implements MyService {     @Activate     void activate(BundleContext ctx) { /* init */ }     @Override     public String doWork() { return "done"; } } 

19. Further reading and resources

  • Eclipse RCP and Plug-in Developer Guides (official docs)
  • Articles on OSGi and Declarative Services
  • SWT and JFace UI guides
  • Tycho and p2 build tooling documentation
  • SWTBot / Jubula testing guides

This collection of best practices and patterns is designed to be a practical reference for teams building Eclipse RCP and plug-in-based applications. Apply them selectively to your project context; small, iterative improvements tend to pay off more than attempting a large, risky refactor all at once.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *