The release of retepTools 9.2 this week introduces a new annotation processor which validates the use of some of the annotations provided by the library generating either compilation warnings or errors dependent upon the errors found. This article describes how that processor works and the reasons behind it’s creation.
In the previous article I wrote about problems in reading values from Annotations in super types. That issue had cropped up due to some of the rules required for this processor.
Now this processor is still in it’s early stages of development but it works and is therefore useable. The rules that follow are those it will enforce and those will not change – although I will almost certainly add more over time.
The rules
Each rule defined below follows this template:
package.Annotation
Action when violated: Compile time error/warning
Mutually excludes: package.AnotherAnnotation…
Related: package.RelatedAnnotation
The Action when violated indicates if an error or warning wouild be produced if that rule is violated.
If the Mutually excludes line is present, then a compile time error will be generated if the annotation is present on an element with any of the listed annotations.
If the Related line is present, it lists annotations that are related and can affect the outcome of this rule.
1 Concurrency RULES
The main set of rules are for concurrency. In previous versions there’s a set of annotations which inject code into the methods they annotate ensuring those methods run within a specific lock – either a standard java.util.concurrent.locks.ReentrantLock or a java.util.concurrent.locks.RentrantReadWriteLock. The problem was that without compile time checking it would be possible to mark a method in one class as being a ReadLock, but then override that method in a subclass and annotate it with WriteLock. Now this would cause an immediate deadlock.
So here are the rules enforced for concurrency:
net.jcip.annotations.NotThreadSafe
Action when violated: Compile time error
Mutually excludes: net.jcip.annotations.ThreadSafe
This annotation is still optional, however it should be declared on a class that may be used in a concurrent context to indicate that it does not use any concurrency. For example an implementation of List should be marked @NotThreadSafe, but a ConcurrentList implementation with @ThreadSafe.
When it is present, this rule will generate a compile time error if any of the concurrency annotations are used in that class. Also subclasses cannot be ThreadSafe due to this class being NotThreadSafe.
For obvious reasons this annotation is mutually exclusive with NotThreadSafe
net.jcip.annotations.ThreadSafe
Action when violated: Compile time error
Mutually excludes: net.jcip.annotations.NotThreadSafe
For all classes that use any of the concurrency annotations must now be marked as ThreadSafe. The reason behind this is to ensure that documentation indicates that the class is ThreadSafe, but also to ensure that subclasses are also ThreadSafe as they can affect the concurrency of the super class. Because of this, a subclass of a class annotated with ThreadSafe must also be marked as ThreadSafe.
For obvious reasons this annotation is mutually exclusive with NotThreadSafe
uk.org.retep.annotations.Lock
Action when violated: Compile time error or warning
Mutually excludes: uk.org.retep.annotations.ReadLock, uk.org.retep.annotations.WriteLock
Related: uk.org.retep.annotations.Contract
This annotation indicates that the method runs within a shared lock. As defined by the annotations javadoc, it injects code into the method to gain the lock, run the method body and then release the lock.
This rule ensures that:
- The method cannot be annotated with a ReadLock or WriteLock as those annotations are mutually exclusive with this one – generates an error.
- If the method is overridden then a warning is issued that the overridden method code will be running outside of the lock.
- As there’s a contract defined in the javadoc for a method called lock() to be defined then that method should be annotated with @Contract( Lock.class ) to document that it’s bound by that Contract. If it is not then a warning is generated.
- The Contract for the support lock() method that is enforced is that the method has one of the following signatures. If it does not have these signatures then an error is generated: private java.util.concurrent.locks.Lock lock(); or protected final java.util.concurrent.locks.Lock lock();
- The class is annotated with @ThreadSafe.
uk.org.retep.annotations.ReadLock
Action when violated: Compile time error or warning
Mutually excludes: uk.org.retep.annotations.Lock, uk.org.retep.annotations.WriteLock
Related: uk.org.retep.annotations.Contract
This annotation indicates that the method runs within a shared read lock. As defined by the annotations javadoc, it injects code into the method to gain the lock, run the method body and then release the lock.
This rule ensures that:
- The method cannot be annotated with a Lock or WriteLock as those annotations are mutually exclusive with this one – generates an error.
- If the method is overridden then a warning is issued that the overridden method code will be running outside of the lock.
- As there’s a contract defined in the javadoc for a method called readLock() to be defined then that method should be annotated with @Contract( ReadLock.class ) to document that it’s bound by that Contract. If it is not then a warning is generated.
- The Contract for the support readLock() method that is enforced is that the method has one of the following signatures. If it does not have these signatures then an error is generated: private java.util.concurrent.locks.Lock readLock(); or protected final java.util.concurrent.locks.Lock readLock();
- The class is annotated with @ThreadSafe.
uk.org.retep.annotations.WriteLock
Action when violated: Compile time error or warning
Mutually excludes: uk.org.retep.annotations.Lock, uk.org.retep.annotations.ReadLock
Related: uk.org.retep.annotations.Contract
This annotation indicates that the method runs within a shared lock. As defined by the annotations javadoc, it injects code into the method to gain the lock, run the method body and then release the lock.
This rule ensures that:
- The method cannot be annotated with a Lock or ReadLock as those annotations are mutually exclusive with this one – generates an error.
- If the method is overridden then a warning is issued that the overridden method code will be running outside of the lock.
- As there’s a contract defined in the javadoc for a method called writeLock() to be defined then that method should be annotated with @Contract( WriteLock.class ) to document that it’s bound by that Contract. If it is not then a warning is generated.
- The Contract for the support writeLock() method that is enforced is that the method has one of the following signatures. If it does not have these signatures then an error is generated: private java.util.concurrent.locks.Lock writeLock(); or protected final java.util.concurrent.locks.Lock writeLock();
- The class is annotated with @ThreadSafe.
2 Singletons
There are two annotations provided for supporting singletons:
uk.org.retep.annotations.NoInstance
Action when violated: Compile time error
Mutually excludes: uk.org.retep.annotations.Singleton
A class marked with NoInstance implies that there can be no instance of this class – i.e. a class with just static fields or methods.
This rule will generate an error if the class violates any of the following:
- The class is not declared final as it cannot have subclasses
- The class has a non private default constructor
- The class has a non default constructor
- The class has any instance methods or fields
- The class has a field referencing itself – i.e. private static Class instance;
- The class has a method who’s return type is that of the class
- The class is annotated with Singleton as its mutually exclusive with NoInstance
uk.org.retep.annotations.Singleton
Action when violated: Compile time error
Mutually excludes: uk.org.retep.annotations.NoInstance
A class marked with Singleton implies that there is only a single instance of this class .
This rule will generate an error if the class violates any of the following:
- The class is not declared final as it cannot have subclasses
- The class has a non private default constructor
- The class has a non default constructor
- The class has no instance methods or fields
- The class does not have a private static field referencing itself – i.e. private static Class instance;
- The class does not have a public static method who’s return type is that of the class
- The class is annotated with NoInstance as its mutually exclusive with Singleton
3 Miscellaneous
This set of rules are not associated with any annotations but enforce certain optional rules.
3.1 hashCode and Equals
When enabled ensures that if a class overrides either of the hashCode() or equals() methods then a compiler error is issued if the class does not override both of them. This is because there is a contract between those two methods where equals() can return true only if the hashCode of both object are also equal (read the docs for java.lang.Object if you disagree).
This rule came about because recently I had a class that was misbehaving in a map and it was because it had not defined both methods.
This rule is enabled by default and can be turned off by passing a configuration parameter to javac.
3.2 Missing javadoc comments
When enabled this rule will generate either a compiler warning or error if a non-private method has no documentation. This option is disabled by default but can be enabled by passing a configuration parameter to javac. The type of action when the rule is violated is itself configurable for this rule.
4 Configuration
The processor has some level of configurability, enabling certain rules to be enabled or disabled depending on user requirements.
4.1 Javac
When using the javac command you can pass any of the following options on the command line, prefixing them with -A. They then take a single value, either true or false to turn that option on or off. For example to turn on the missing javadoc rule, then you would pass -AwarnMissingJavadocs=true to javac.
4.2 Maven
When using the maven compiler plugin you are supposed to be able to add the javac options to the pom by using the compilerArgument attribute:
<compilerArgument>-AfailHashCodeEquals=true -AwarnMissingJavadocs=true</compilerArgument>
The problem is that although this is shown in the plugins documentation, it doesn’t work as it gets passed to javac as a single argument and not as a set of arguments.
To get around this the processor also supports a special option called mavenOpts who’s value is a comma separated list of the required features – if the feature is in the string then it is enabled. To disable a feature then simply prefix the feature with either ! or ^ – there’s two options to negate as some shells use ! so it’s not always possible to use on the command line:
<compilerArgument>-AmavenOpts=failHashCodeEquals,^warnMissingJavadocs</compilerArgument>
4.3 Currently supported options
failHashCodeEquals
Should an error occur if one of hashCode or equals is overridden but not the other? Enabled by default
warnMissingJavadocs
If enabled a compiler warning is generated for each non private method or field that has no documentation.
failMissingJavaDocs
This overrides warnMissingJavadocs if enabled. This will cause a compiler error to be issued if a non private method or field has no documentation.
Really awesome read. Really.