Extending the client

jawi edited this page Dec 23, 2011 · 5 revisions

Table of Contents

Extension points

The OLS client is extensible at several points. The following extension types are defined:

  1. menus, allowing you to add new menus to the general menu bar in the client;
  2. devices, denoting physical devices the OLS client can talk to;
  3. tools, denoting tools that perform certain actions on sample data;
  4. exporters, denoting functionality to export sample data to a certain format, such as images;
  5. importers, denoting functionality to import external data formats and represent them as sample data. This extension point is not yet available.
All extensions are written in Java, and have a few dependencies in order to work. They are packaged in JAR-files, in which one JAR file can contain multiple extension points of the same type, or of different types. Though it is not mandatory, it is adviced to use Maven as build tool. A typical POM-file looks like:
    <dependencies>
        <dependency>
            <groupId>nl.lxtreme.ols</groupid>
            <artifactId>api</artifactid>
            <version>1.0.4</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>nl.lxtreme.ols</groupid>
            <artifactId>util</artifactid>
            <version>1.0.5</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupid>
            <artifactId>org.osgi.core</artifactid>
            <version>4.2.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupid>
            <artifactId>org.osgi.compendium</artifactid>
            <version>4.2.0</version>
            <scope>compile</scope>
        </dependency>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupid>
                <artifactId>maven-bundle-plugin</artifactid>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <OLS-Exporter>1.0</ols-exporter>
                        <OLS-ExporterClass>nl.lxtreme.ols.export.funky.MyFunkyExporter</ols-exporterclass>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>
  </project>

There are several "section" in the POM-file (denoted with empty lines), the first one is the project information itself, containing of a name, group identifier, version and so on. The second section is the list of dependencies. Unless you're making something really spiffy, the dependencies listed above are enough. The last section is are the build instructions, which instruct Maven how the resulting JAR file should look like. In fact, it instructs how to MANIFEST.MF file of the resulting JAR should look like. While most of this is rather "static", special care needs to be taken for the tags inside the instructions container (the OLS-Exporter and OLS-ExporterClass). These are specific to the OLS-client, and are verbatimly written to the MANIFEST.MF file.

Upon startup of the OLS-client, the manifests of all JARs are scanned for lines starting with "OLS-". If found, it will be regarded as a potential extension point. These instruction lines above tell the OLS-client two things:

  1. the JAR contains one or more exporter extensions;
  2. the main class of this exporter extension is nl.lxtreme.ols.export.funky.MyFunkyExporter.
NOTE: both mentioned lines are needed! If one of them is omitted from the resulting JAR file, it will be considered an invalid extension and the results are not (well) defined!

For all extension points, interfaces are defined in the api project inside the client's source repository. These interfaces are to be implemented by the extensions in order to operate correctly. Extensions that do not implement these interface(s) will not operate correctly.

Aside the exporters, as mentioned before, the following types of extensions, each working in a similar fashion as the exporters mentioned above:

  1. OLS-Tool and OLS-ToolClass, for providing additional protocol decoders, or other tools that work upon acquisition results;
  2. OLS-Device and OLS-DeviceClass, for providing (non SUMP-compatible) devices that can provide acquisition results;
  3. OLS-ComponentProvider and OLS-ComponentProviderClass, for providing additional UI-components to the client.

Exporters

Exporters provide functionality to "export" acquisition results to external files, such as images, or data formats supported by other tooling. Exporters are supposed to inherit from the interface nl.lxtreme.ols.api.data.export.Exporter, and provides the following three methods:

  1. export, which is called to perform the "real" export. It is given the current acquisition results, and an output stream to write the results to;
  2. getFilenameExtensions, which returns an array of the possible extensions for the export file name. The first item of the returned array is considered the "preferred" extension of the file name;
  3. getName, which returns a symbolic name for the exporter, used for displaying purposes in the client, such as the menu item.
For more information about the details on these methods, see the JavaDoc of the Exporter interface. An example implementation can be found in the exporter.image project in the source repository.

Devices

Devices provide acquisition results from external or internal data sources, such as the LogicSniffer hardware. Devices are supposed to inherit from the interface nl.lxtreme.ols.api.devices.Device (prior to 0.9.5 this was nl.lxtreme.ols.api.devices.DeviceController), and provides the following methods:

  1. createAcquisitionTask, which is called when an acquisition is to be started. This method should return a nl.lxtreme.ols.api.devices.AcquisitionTask implementation that is capable of performing the acquisition;
  2. createCancelTask, which is called if the device is acquiring data and the user wants to cancel (or abort) it. This method should return an nl.lxtreme.ols.api.devices.CancelTask implementation that is capable of performing the cancel. This method may also return null in which case the acquisition is aborted by terminating the ongoing AcquisitionTask;
  3. close, which is called after an acquisition is done or cancelled. It allows the device to be set in a proper (default) state and release all resources that were needed during the acquisition;
  4. getName, which returns a symbolic name for the device, used for displaying purposes in the client, such as the menu item in the user interface;
  5. isSetup, provides a status callback whether or not the device is set up properly and can be used directly to recapture data;
  6. setupCapture, which is called when the device is not set up, or when the user chooses to set up the device again. This method can display a dialog to allow the user to change the settings and/or configuration of the device.
For more information about the details on these methods, see the JavaDoc of the DeviceController interface. An example implementation can be found in the device.generic (easy) or device.logicsniffer (difficult!) projects in the source repository.

Tools

Tools can do "stuff" on earlier obtained acquisition results, for example, to provide additional information on the captured data (such as protocol information, or timing information). Tools are supposed to inherit from the nl.lxtreme.ols.api.tools.Tool interface and provides the following three methods:

  1. createToolTask, which creates a worker to perform the actual task of the implementing tool. This method should return an implementation of nl.lxtreme.ols.api.tools.ToolTask. Information about the current context is provided to the method by means of the ToolContext class. Two additional "callbacks" are provided as well, the ToolProgressListener allows you to report the current progress of the tool, and AnnotationListener allows you to publish new annotations to the sample data;
  2. getCategory which is used to determine the "category" of a tool. There are three categories defined: DECODER, MEASURE and OTHER. It is used to categorize tools;
  3. getName, which returns a symbolic name for the tool, used for displaying purposes in the client, such as the menu item;
  4. invoke, which is called by the user interface to let the tool do its job. As it is called by the user interface, the tool can present the user some configuration option prior to starting the actual job.
To simplify the development of custom tools, a small API is created in the tool.base project. This project provides a base implementation for dialogs (nl.lxtreme.ols.tool.base.BaseToolDialog) that tools can derive from. It hides most (if not all) of the complexity of OSGi, allowing you to simply write plain Java code for your tool. Additionally, some common used UI-elements, such as close/export/run-buttons, can easily be created by using the ToolUtils helper class.

An additional advantage to using the tool.base project is that it provides two default annotations that you can use in your tool: ChannelLabelAnnotation and SampleDataAnnotation. The first allows you to set a channel label to something meaningful, while the latter provides you the ability to add an annotation to the sample data. Along with the AnnotationListener adding an annotation is very easy:

   this.annotationListener.onAnnotation( new SampleDataAnnotation( channelIdx, startSample, endSample, "My Annotation" ) );

For more information about the details on these methods, see the JavaDoc of the Tool interface, and the interfaces/classes in the tool.base project. An example implementation can be found in the tool.i2c or tool.measure projects in the source repository.

ComponentProviders

Component providers allow for additional UI components to be supplied to the client. Currently, only menus can be provided through this extension point. Component providers should inherit from the interface nl.lxtreme.ols.api.ui.ComponentProvider and implement the following methods:

  1. addedToContainer, which is called after the provided component is added to its parent container. This method can be used to add event listeners to the parent component or any other component that need to be done in context of the component hierarchy;
  2. getComponent, which returns the actual component (for now: the javax.swing.JMenu) instance. Note that this method is called at least once, and before addedToContainer is called. Note that this method is not a factor method, so implementors should return the exactly one component instance for succeeding calls to this method;
  3. removedFromContainer, which is called right before the provided component is removed from its parent container. This method can be used to remove any event listeners from the parent component (e.g. the inverse operations as is called in the #addedToContainer).
For more information about the details on these methods, see the JavaDoc of the ComponentProvider interface. A short tutorial on how to write your own component providers can be found in this [ComponentProvider-Tutorial].

Implementation hints

  • To obtain the OSGi BundleContext in your extension point, you should create a method with the signature init(org.osgi.framework.BundleContext) in your extension point. The visibility of this method does not matter, but it is good practise to make it protected. When deploying an extension point, this method will be called before any other methods of the extension point is called. The BundleContext passed to the method provides access to the OSGi context in which the extension point was deployed. You need a BundleContext to interact with the OSGi framework, for example, to obtain additional services. Note that for writing tools, you can simply use the BaseToolDialog instead;
  • If you want to develop an extension that looks similar to an existing extension, you can simply copy the code of the existing extension and modify it to match your needs. As long as you ensure the naming is unique, it will be picked up and regarded as unique plugin;
  • When developing your extension, log as many as possible. The current convention for logging is the JDK Log implementation. It is an easy API and provide enough flexibility to log at various detail levels;
  • If possible, unit test your code with existing data files providing capture results. It allows you to easy determine whether certain changes improve or degrade your extension's implementation.