Description
A simple maven plugin that detects some illegal changes to a Java project's API.
How it works
APILyzer looks for deviations from the following rules.
- Public API members should use other Public API types.
- Public API members should use an approved set of types.
APILyzer allows easy declaration of Public API Types and approved types. The public members of the public API types are analyzed to ensure only expected types are used. Public members include public methods, public fields, and public inner classes. Protected classes and members are treated as public during analysis.
Other tools
This tool fills a niche not covered by other tools, like Animal Sniffer and checkstyle import control. A project that wants to ensure API stability would likely use these tools in addition to APILyzer. Import analysis differs because it's ok for a public API class to import a non-public API class for use only in its implementation. Comparing new API changes to signatures of a previous API differs because it only validates that the old API is not broken.
How to use it
To add this plugin to your project, configure the plugin similarly to:
<build>
<plugins>
<plugin>
<groupId>net.revelc.code</groupId>
<artifactId>apilyzer-maven-plugin</artifactId>
<version>1.3.0</version>
<executions>
<execution>
<id>apilyzer</id>
<goals>
<goal>analyze</goal>
</goals>
<configuration>
<includes>
<!--Specify one or more regular expressions that define the
public API. Each regex is matched agains all fully
qualified class names. Any class that matches (and is
public) is added to the set of public API classes.-->
<include>org[.]apache[.]accumulo[.]minicluster[.].*</include>
</includes>
<excludes>
<!-- Specifiy zero or more regular expressions. Any regex that
matches will exclude a prevously included class from the
set of API classes -->
<exclude>.*[.]impl[.].*</exclude>
<exclude>.*Impl</exclude>
</excludes>
<allows>
<!-- Specify zero or more regular expressions defining the set
of non-API classes thats it ok for public API members to
reference. These regular expressions are matched against
fully qualified type names referenced by public API
members. Conceptually, public API classes and Java classes
are automatically added to this set, so there is no need
to add those here. -->
<allow>org[.]apache[.]accumulo[.]core[.]client[.].*</allow>
<allow>org[.]apache[.]accumulo[.]core[.]data[.](Mutation|Key|Value|Condition|ConditionalMutation|Range|ByteSequence|PartialKey|Column)</allow>
<allow>org[.]apache[.]accumulo[.]core[.]security[.](ColumnVisibility|Authorizations)</allow>
</allows>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
and build your project, similarly to (it runs at the verify phase by default):
mvn verify
Below is the output of running the above command.
Includes: [org[.]apache[.]accumulo[.]minicluster[.].*]
Excludes: [.*[.]impl[.].*, .*Impl]
Allowed: [org[.]apache[.]accumulo[.]core[.]client[.].*, org[.]apache[.]accumulo[.]core[.]data[.](Mutation|Key|Value|Condition|ConditionalMutation|Range|ByteSequence|PartialKey|Column), org[.]apache[.]accumulo[.]core[.]security[.](ColumnVisibility|Authorizations)]
Public API:
org.apache.accumulo.minicluster.MemoryUnit
org.apache.accumulo.minicluster.MiniAccumuloCluster
org.apache.accumulo.minicluster.MiniAccumuloConfig
org.apache.accumulo.minicluster.MiniAccumuloInstance
org.apache.accumulo.minicluster.MiniAccumuloRunner
org.apache.accumulo.minicluster.MiniAccumuloRunner$Opts
org.apache.accumulo.minicluster.MiniAccumuloRunner$PropertiesConverter
org.apache.accumulo.minicluster.ServerType
CONTEXT TYPE FIELD/METHOD NON-PUBLIC REFERENCE
Method return org.apache.accumulo.minicluster.MiniAccumuloInstance getConfigProperties(...) org.apache.commons.configuration.PropertiesConfiguration
Method param org.apache.accumulo.minicluster.MiniAccumuloInstance lookupInstanceName(...) org.apache.accumulo.fate.zookeeper.ZooCache
The output shows the 8 types that APILyzer determined to be public API types based on its configuration and the maven dependencies. These are the 8 types that APILyzer analyzed.
The output also shows two problems APILyzer found with these 8 types. Both
problems are with public API methods. The first problem is
MiniAccumuloInstance.getConfigProperties()
returns PropertiesConfiguration
which is not a public API type. The second problem is
MiniAccumuloInstance.lookupInstanceName()
takes a parameter of type
ZooCache
which is not a public API type.
Annotation Example
Hadoop has two annotations that it uses to communicate who should use its APIs. First, the
InterfaceAudience
annotation has values of Public
, LimitedPrivate
, and Private
. Second, the
InterfaceStability
annotation has values of Stable
, Evolving
, and Unstable
. In Hadoop the
convention is to mark each API with an audience and stability annotation. The following example
finds types marked Public+Stable using types that are not Public+Stable.
When trying to analyze a classes annotations, the class must be loaded. In the example below some
classes could not be loaded because their dependencies were not on the classpath. However these
classes were not in a package we cared about. Excluding classes not in the org.apache.hadoop
package fixed this problem.
<configuration>
<!--Look for Public+Stable APIs using Types that are not Public+Stable-->
<includes>
<!-- This class has no annotation, but seems like it sould be in API -->
<include>org[.]apache[.]hadoop[.]fs[.]RemoteIterator</include>
</includes>
<includeAnnotations>
<include>
[@]org[.]apache[.]hadoop[.]classification[.]InterfaceAudience[$]Public.*
</include>
</includeAnnotations>
<excludeAnnotations>
<exclude>
[@]org[.]apache[.]hadoop[.]classification[.]InterfaceStability[$]Evolving.*
</exclude>
<exclude>
[@]org[.]apache[.]hadoop[.]classification[.]InterfaceStability[$]Unstable.*
</exclude>
<!-- If a class is included in the API, then its public inner classes are also (unless excluded).
This exclude is for the case where a type w/ Private annotation is an inner class of a class
with a Public annotation -->
<exclude>
[@]org[.]apache[.]hadoop[.]classification[.]InterfaceAudience[$](Limited)?Private.*
</exclude>
</excludeAnnotations>
<excludes>
<!-- Exclude all classes not in the org.apache.hadoop package -->
<exclude>(?!org[.]apache[.]hadoop.*).*</exclude>
</excludes>
</configuration>