Defining APIs is crucial to maintaining modularity. OSGi defines the concepts necessary for API definition such as a service concept and package visibility. However, pure OSGi is not enough to really maintain an API and its potential usages. There are several cases in an API definition where it is required to do more than restrict the accessibility to packages. As an example, a public package might contain an interface, which defines public methods to be used by clients. However, internally the bundle might be restricted to using a certain implementation of this interface. Therefore the client is not expected to provide its own implementations of this interface, but only to use it to access objects. This cannot be expressed with pure Java or OSGi.
Additionally, bundles that define an API are required to maintain compatibility until the next major release. Therefore users of a framework can expect that bundles do not break their API until the major version increases, e.g. from 1.x to 2.x. Between major releases, the API is allowed to be extended if the minor version is increased, e.g. from 1.1 to 1.2.
While it is very obvious that these rules make sense for open source frameworks that are used by hundreds of projects, even in a medium-sized inhouse project, it is often useful to follow these guidelines to coordinate different teams and components. However, the best rules are worthless if there isn’t good tooling to enforce them.
Fortunately, the Plug-in Development Environment (PDE) has provided the API Tooling for quite some time. It allows the tracking of changes to a bundle defining an API. Whenever the API is changed, it provides errors until the bundle is configured according to the above mentioned rules. This means that in case of additions to the API, they need to be annotated with an @Since tag and the minor version number needs to be increased. If there is a breaking change to the API, the major version number of the bundle needs to be increased. Additionally the API tooling provides annotations that allows additional restrictions on the usage of the API. For example these annotations can define an interface not to be implemented by clients.
API Compatibility Check
Enabling these checks on a bundle is actually pretty simple and requires only two steps. The first one is to define a base line. That is, the last release of a bundle or a group of bundles. The API tools will compare the current state of the bundle in your workspace with this baseline to determine if there are API changes. The base line needs to be configured within the developer’s workspace. Under Preferences => Plug-In Development => API Baselines, you can add folders which contain the binaries of the releases you want your bundles to be compared with.
Once the base line is set, you can enable the API Tools for a specific plug-in with a right click on the plug-in and selecting Plug-in Tools => API Tools Setup. This will add an API Tools nature to the project and enable the API check.
Now if you change parts of the API, the tooling will provide errors. In the following example, a new method is added to an interface which is part of the API of a bundle. The last release of the bundle was version 1.0.0. In this case, the API tooling will show two errors. Additions to the API, e.g. new methods in an interface, must be marked with a @Since annotation, specifying since which version the additions are available.Therefore, the first error indicates, that the new method must be marked with a @Since annotation. The second one indicates that the minor version of the bundle needs to be increased as new API has been added:
The second error should be fixed first by increasing the version number. After that, the @Since annotation can be added using a quickfix. If you add the @Since tag first, it will automatically add the current version (1.0.0) of the bundle which, in this case, is the wrong one. This will result in another error saying that the since tag is invalid.
The API tooling defines several annotations which restrict the use of interfaces and classes which are part of the API. All annotations are placed in front of the restricted element as part of the Javadoc, e.g.:
Depending on the IDE settings of an API user, violations of the restrictions will cause errors, warnings or will be ignored. The following annotations are supported:
- @noimplement: Clients are not allowed to implement an interface
- @noextend: Clients are not allowed to extend a class or interface
- @noinstanciate: Clients are not allowed to instantiate a class
- @nooverride: Clients are not allowed to override a method
- @noreference: Clients are not allowed to reference a method, a constructor or a non-final field.