Extending Revapi

Extension points

The architecture overviews sports a simple diagram that hints at several extension points available in Revapi. Each extension point is configurable and can provide a JSON schema for its configuration.

Archives

An Archive is a very simple abstraction over a file that represents the archive containing some API elements (like Java’s jar archive or a class file, an XML document, properties file, etc).

An archive can also optionally be versioned - some extensions may take advantage of that. If you are able to provide a version of an archive, you should supply an implementation of the Archive.Versioned interface instead of just a plain Archive.

Revapi provides just a basic file-based implementation of this interface, but it is trivial to implement one. For example the maven extension has its own implementation of the Archive.Versioned interface which is able to return the version of the underlying Maven artifact.

There is a simple example for writing custom archive implementations.

API Analyzer

An API analyzer is the main interface for implementing API checks for custom "language". It provides and configures the analyzers of the archives and API difference analyzers both detailed in the example project.

There is an example showing a custom api analyzer implementation.

Archive Analyzer

An archive analyzer is instantiated and configured by the API analyzer to analyze archives of a version of API. It represents the results of the analysis as an element forest (i.e. a set of element trees). The archive analyzers can take advantage of the tree filters to leave out parts of the elements and save some processing power.

There is an example showing a custom archive analyzer implementation.

Tree Filter

An tree filter can filter out elements from the element forest before they are passed further down the API analysis pipeline. The same set of element filters is applied to both the old API and new API element forests.

There is an example showing a custom tree filter implementation.

Element Matcher

An element matcher is a kind of "helper" extension that can be used by other extensions, like tree filters or difference transforms to identify the elements matching some user-defined criteria.

There is an example showing a custom element matcher implementation.

Difference Analyzer

The magic happens in the difference analyzers. Revapi simultaneously traverses the two element forests discovering new or removed elements and matching the comparable elements in them (using a co-iterator). It then passes the matched pairs to the difference analyzer that performs the actual analysis of changes and provides the reports summarizing them.

A report summarizes the differences found between 2 elements - one from the old API and the other from the new API (accounting for removals or additions by one of the pair being null).

In addition to the two elements in comparison, the report also contains the list of the differences the analyzer found between the two.

There is an example showing a custom difference analyzer implementation.

Difference Transform

Once the differences are found they are supplied to the difference transforms. These extensions can, as the name suggests, transform the found differences into different ones or altogether remove them from the results.

There is an example showing a custom difference transform implementation.

Reporter

Finally, after the final set of differences is settled, it is passed to the reporters. These are responsible to report the found differences to the caller somehow (standard output, database, xml files, whatever one imagines).

There is an example showing how to write a custom reporter implementation.

Analysis Workflow

The caller is first required to first supply the pipeline configuration to tell Revapi what extensions it will have available for analysis and other configuration.

The analysis is then executed using an analysis context. This context contains the two APIs that should be compared as well as the configuration of the extensions for that particular analysis.

The following diagram provides a detailed picture of the analysis workflow. The objects marked by the circled E represent a collection of extension instances of respective kinds which can be supplied to Revapi. Each such extension can be configured by the user.

Diagram

API Traversal

All the elements produced by a single API analyzer need to be mutually comparable. The element trees produced by the archive analyzers are sorted. Revapi takes the advantage of this fact when looking for the changed elements.

The traversal is performed in the depth-first manner. Let’s consider the below two API trees. Their names encode the position in the graph so that we can then illustrate the API traversal in text. o_ and n_ prefixes mean that the element comes from the old (o_) or new (n_) API. The letter following the prefix indicates the "name" and is used merely for identification purposes. Finally, the number following the name indicates the "order" of the element amongst all the elements with the same name in both APIs.

These pairs are then supplied to the difference analyzers that produce the lists of found differences between these pairs.

Diagram

The API traversal goes through both of the trees at the same time and produces pairs of elements where the first element comes from the old API and the second from the new API. Either of those elements can be null indicating that there is no adequate counterpart in the other API.

So let’s start the traversal…​

Diagram

(o_a1, n_a1) The elements o_a1 and n_a1 are considered equal (they have the same name and same "order"). When we have a match, we report them and dive into their children.

Diagram

(o_b1, n_b1) Here, we’re in the same situation as before. The elements compare as equal and therefore we’re diving a level further.

Diagram

(o_c1, n_c1) Again, the elements are matching and therefore they are both reported at the same time. There are no children to iterate so we’re continuing to the siblings.

Diagram

(o_c1, null) Here we see the first "odd" thing. Only o_c2 is reported and no element from the new API. This is because we’ve found no matching element in the new API. n_c4 is considered "greater".

Diagram

(null, n_c4) Here we see a similar situation only with the new API. We’ve already reported o_c2 and there is no further element to report in the old API. But we need to report n_c4 which is "greater" than all the elements in the old API. Therefore, we report it. We’ve depleted all siblings in both APIs so we continue in the upper level.

Diagram

(o_b2, n_b2) These two elements match and are therefore reported together. Let’s dive into children.

Diagram

(null, n_d1) Here we see, that the element n_d1 from the new API is considered "less" than all other siblings in the old API. Therefore, it is reported first and alone.

Diagram

(o_d2, null) The next in line for the combined sets of siblings is o_d2. It has no counterpart in the new API and therefore it is again reported alone.

Diagram

(o_d3, null) The next in line is o_d3. It again doesn’t have a matching counterpart in the new API and so is again reported alone.

Diagram

(o_d4, n_d4) Now we arrive at elements that are considered equal in both APIs so they’re reported together.

Diagram

(o_b3, null) In the previous step we finished visiting all the "d" siblings and therefore now we’re on the "b" level. Here, the next in line is o_b3 that has no matching counterpart in the new API.

Diagram

(null, n_b4) And at last we’ve arrived at the last unreported element, n_b4. We’ve visited all the elements in both APIs.

Packaging Extensions

Extensions should be packaged as ordinary jar files. Revapi is as of yet not fully modularized (it only defines the automatic module names) so it is recommended to place Revapi and all extensions on the classpath, not the modulepath.

The convention for finding Revapi extensions is to find them using the service loader. Therefore, if you want your extension to be found by the revapi-maven-plugin for example, you need to make sure you place the appropriate service file in META-INF/services or your jar (and subsequently add that jar as a dependency of the revapi-maven-plugin).

E.g., if you define a new difference transform in your extension, called com.acme.AcmeDifferenceTransform,you need to create a file called org.revapi.DifferenceTransform in the META-INF/services directory of you jar. Each line in that file should contain a fully qualified class name of an implementation of the difference transform.

Take a look at the example extensions which define these service files.