Way to write java annotation processor to require method call? [duplicate] - java

Let's say I define a custom annotation called #Unsafe.
I'd like to provide an annotation processor which will detect references to methods annotated with #Unsafe and print a warning.
For example, given this code ...
public class Foo {
#Unsafe
public void doSomething() { ... }
}
public class Bar {
public static void main(String[] args) {
new Foo().doSomething();
}
}
... I want the compiler to print something like:
WARN > Bar.java, line 3 : Call to Unsafe API - Foo.doSomething()
It is very similar in spirit to #Deprecated, but my annotation is communicating something different, so I can't use #Deprecated directly. Is there a way to achieve this with an annotation processor? The annotation processor API seems to be more focused on the entities applying the annotations (Foo.java in my example) than entities which reference annotated members.
This question provides a technique to achieve it as a separate build step using ASM. But I'm wondering if I can do it in a more natural way with javac & annotation processing?

I think I could have technically achieved my goal using the response from #mernst, so I appreciate the suggestion. However, I found another route that worked better for me as I'm working on a commercial product and cannot incoporate the Checker Framework (its GPL license is incompatible with ours).
In my solution, I use my own "standard" java annotation processor to build a listing of all the methods annotated with #Unsafe.
Then, I developed a javac plugin. The Plugin API makes it easy to find every invocation of any method in the AST. By using some tips from this question, I was able to determine the class and method name from the MethodInvocationTree AST node. Then I compare those method invocations with the earlier "listing" I created containing methods annotated with #Unsafe and issue warnings where required.
Here is an abbreviated version of my javac Plugin.
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskEvent.Kind;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreeScanner;
public class UnsafePlugin implements Plugin, TaskListener {
#Override
public String getName() {
return "UnsafePlugin";
}
#Override
public void init(JavacTask task, String... args) {
task.addTaskListener(this);
}
#Override
public void finished(TaskEvent taskEvt) {
if (taskEvt.getKind() == Kind.ANALYZE) {
taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
#Override
public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) {
Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect());
TypeElement invokedClass = (TypeElement) method.getEnclosingElement();
String className = invokedClass.toString();
String methodName = methodInv.getMethodSelect().toString().replaceAll(".*\\.", "");
System.out.println("Method Invocation: " + className + " : " + methodName);
return super.visitMethodInvocation(methodInv, v);
}
}, null);
}
}
#Override
public void started(TaskEvent taskEvt) {
}
}
Note - in order for the javac plugin to be invoked, you must provide arguments on the command line:
javac -processorpath build/unsafe-plugin.jar -Xplugin:UnsafePlugin
Also, you must have a file META-INF/services/com.sun.source.util.Plugin in unsafe-plugin.jar containing the fully qualified name of the plugin:
com.unsafetest.javac.UnsafePlugin

Yes, this is possible using annotation processing.
One complication is that a standard annotation processor does not descend into method bodies (it only examines the method declaration). You want an annotation processor that examines every line of code.
The Checker Framework is designed to build such annotation processors. You just need to define a callback that, given a method call and issues a javac warning if the call is not acceptable. (In your case, it's simply whether the method's declaration has an #Unsafe annotation.) The Checker Framework runs that callback on every method call in the program.

The AbstractProcessor below processes greghmerrill's #Unsafe annotation and emits warnings on method calls to #Unsafe annotated methods.
It is a slight modification of greghmerrills own answer, which was great, but I had some problems getting my IDEs incremental compiler (I am using Netbeans) to detect the warnings/errors etc emitted from the plugin - only those I printed from the processor was shown, though the behaviour was as expected when I ran 'mvn clean compile' ( I am using Maven). Whether this is due to some problem from my hand, or a points to difference between Plugins and AbstractProcessors/the phases of the compilation process, I do not know.
Anyway:
package com.hervian.annotationutils.target;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.util.*;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import java.util.Set;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
#SupportedAnnotationTypes({"com.hervian.annotationutils.target.Unsafe"})
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public class UnsafeAnnotationProcessor extends AbstractProcessor implements TaskListener {
Trees trees;
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
trees = Trees.instance(processingEnv);
JavacTask.instance(processingEnv).setTaskListener(this);
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//Process #Unsafe annotated methods if needed
return true;
}
#Override public void finished(TaskEvent taskEvt) {
if (taskEvt.getKind() == TaskEvent.Kind.ANALYZE) {
taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
#Override
public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) {
Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect());
Unsafe unsafe = method.getAnnotation(Unsafe.class);
if (unsafe != null) {
JCTree jcTree = (JCTree) methodInv.getMethodSelect();
trees.printMessage(Diagnostic.Kind.WARNING, "Call to unsafe method.", jcTree, taskEvt.getCompilationUnit());
}
return super.visitMethodInvocation(methodInv, v);
}
}, null);
}
}
#Override public void started(TaskEvent taskEvt) { } }
When using the annotation and making calls to the annotated method it will look like this:
One needs to remember to add the fully qualified class name of the annotation processor to a META-INF/service file named javax.annotation.processing.Processor. This makes it available to the ServiceLoader framework.
Maven users having trouble with the com.sun** imports may find this answer from AnimeshSharma helpful.
I keep my annotation + annotation processor in a separate project. I had to disable annotation processing by adding the following to the pom:
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
Using the annotation and having the processor do its work was simple: In my other project (the one where the screenshot of method foo() is from) I simply added a dependency to the project containing the annotation and processor.
Lastly it should be mentioned that I am new to AbstractProcessors and TaskListeners. I do, fx, not have an overview of the performance or robustness of the code. The goal was simply to "get it to work" and provide a stub for similar projects.

Related

How to access TypeUse annotation via AnnotationProcessor

Question:
Is it possible to access elements annotated with a #Target(ElementType.TYPE_USE) annotation via an annotation processor?
Is it possible to access the annotated type bounds via an annotation processor?
Links to related documentation I missed are highly appreciated.
Context:
The annotation:
#Target(ElementType.TYPE_USE)
#Retention(RetentionPolicy.SOURCE)
public #interface TypeUseAnno {}
An example class:
public class SomeClass extends HashMap<#TypeUseAnno String, String> {}
The processor:
#SupportedSourceVersion(SourceVersion.RELEASE_8)
#SupportedAnnotationTypes("base.annotations.TypeUseAnno")
public class Processor extends AbstractProcessor {
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Initialized.");
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Invoked.");
for (TypeElement annotation : annotations) {
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "" + roundEnv.getElementsAnnotatedWith(annotation));
}
return true;
}
}
Compiling the above SomeClass with Processor on the classpath will show the "Intialized" message but the process(...) method is never invoked.
Adding another annotation to the processor with #Target(ElementType.PARAMETER) works fine when the annotation is present on a method parameter. If the method parameter is annotated with #TypeUseAnno the process will again ignore the element.
The TYPE_USE annotations are a bit tricky, because the compiler treats them differently, than the "old usage" annotations.
So as you correctly observed, they are not passed to annotation processor, and your process() method will never receive them.
So how to use them at compilation time?
In Java 8, where these annotations got introduced, there was also introduced new way to attach to java compilation. You can now attach listener to compilation tasks, and trigger your own traversal of the source code. So your task to access the annotation splits into two.
Hook to the compiler.
Implement your analyzer.
Ad 1.
There are 2 options to hook on the compiler in Java 8:
Using new compiler plugin API.
Using annotation processor.
I haven't used option #1 much, because it needs to be explicitely specified as javac parameter. So I'll describe option #1:
You have to attach TaskListener to the propper compilation phase. There are various phases. Following one is the only one, during which you have accessible syntax tree representing full source code including method bodies (remember, that TYPE_USE annotations can be used even on local variable declarations.
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public class EndProcessor extends AbstractProcessor {
#Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
Trees trees = Trees.instance(env);
JavacTask.instance(env).addTaskListener(new TaskListener() {
#Override
public void started(TaskEvent taskEvent) {
// Nothing to do on task started event.
}
#Override
public void finished(TaskEvent taskEvent) {
if(taskEvent.getKind() == ANALYZE) {
new MyTreeScanner(trees).scan(taskEvent.getCompilationUnit(), null);
}
}
});
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// We don't care about this method, as it will never be invoked for our annotation.
return false;
}
}
Ad 2.
Now the MyTreeScanner can scan the full source code, and find the annotations. That applies no matter if you used the Plugin or AnnotationProcessor approach. This is still tricky. You have to implement the TreeScanner, or typically extend the TreePathScanner.
This represents a visitor pattern, where you have to properly analyze, which elements are of your interest to be visited.
Let's give simple example, that can somehow react on local variable declaration (give me 5 minutes):
class MyTreeScanner extends TreePathScanner<Void, Void> {
private final Trees trees;
public MyTreeScanner(Trees trees) {
this.trees = trees;
}
#Override
public Void visitVariable(VariableTree tree, Void aVoid) {
super.visitVariable(variableTree, aVoid);
// This method might be invoked in case of
// 1. method field definition
// 2. method parameter
// 3. local variable declaration
// Therefore you have to filter out somehow what you don't need.
if(tree.getKind() == Tree.Kind.VARIABLE) {
Element variable = trees.getElement(trees.getPath(getCurrentPath().getCompilationUnit(), tree));
MyUseAnnotation annotation = variable.getAnnotation(MyUseAnnotation.class);
// Here you have your annotation.
// You can process it now.
}
return aVoid;
}
}
This is very brief introduction. For real examples you can have a look at following project source code:
https://github.com/c0stra/fluent-api-end-check/tree/master/src/main/java/fluent/api/processors
It's also very important to have good tests while developing such features, so you can debug, reverse engineer and solve all the tricky issues you'll face in this area ;)
For that you can also get inspired here:
https://github.com/c0stra/fluent-api-end-check/blob/master/src/test/java/fluent/api/EndProcessorTest.java
Maybe my last remark, as the annotations are really used differently by the javac, there are some limitations. E.g. it's not suitable for triggering java code generation, because the compiler doesn't pick files created during this phase for further compilation.

How can I prevent Eclipse from supplying deprecated classes when importing?

I have a recurring problem using Eclipse. Consider the following example:
As you can see I've pressed Ctrl+Shift+O. I can choose from a deprecated and a non-deprecated annotation. My problem is that I am often supplied with dozens of classes and half of them are deprecated (a perfect example is the JUnit Assert classes).
My question is how can I make Eclipse ignore all deprecated classes when organizing imports?
Currently Eclipse does not provide such an option... Eclipse Documentation for Organise Imports (Kepler version).
However, with a fudge you can achieve the same result...
Eclipse allows you to provide a list of classes/packages to filter-out.
To do this, navigate to Preferences > Type Filters.
I've done this in my environment to ensure "java.awt.List" is not suggested when I really want "java.util.List".
What you want is to add all deprecated classes to this list.
This list is maintained in your eclipse workspace preferences...
File ... C:\Users\[YOUR_USER_NAME]\workspace\.metadata\.plugins\org.eclipse.core.runtime\.settings\org.eclipse.jdt.ui.prefs
Property ... org.eclipse.jdt.ui.typefilter.enabled=java.awt.List;
All that is required is that you create a list of deprecated classes, and store it in this properties file.
Eclispe can help create this list...
Perform a "Java Search" for "Deprecated".
Then group the results by type.
And copy the results using "Copy Qualified Name"
The results will contain Generics, and this should be removed.
For example, "javafx.scene.control.Cell<T>" should read "javafx.scene.control.Cell".
In addition to containing deprecated classes, the results will also contain any class that has the word "Deprecated". This could be a comment or a method annotation. This list will need to be filtered to retain only deprecated classes.
The script below processes this class list to remove generics, and filtering out classes that are not deprecated (ie, only has method deprecation). The class list is read from a file named "DeprecatedClassList.txt". When it cannot check the class annotation, it skips the class and prints it out (for manual checking).
import java.lang.annotation.Annotation;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ConfigurationGenerator {
public static void main(String[] args) throws Exception {
List<String> cleanedList = Files
.readAllLines(Paths.get("DeprecatedClassList.txt")).stream()
.map(ConfigurationGenerator::removeGenerics)
.filter(ConfigurationGenerator::hasDeprecatedConstructor)
.collect(Collectors.toList());
String propertyName = "org.eclipse.jdt.ui.typefilter.enabled=";
String propertyValue = String.join(";", cleanedList).concat(";");
String configuration = propertyName + propertyValue;
System.out.println("Configuration property...");
System.out.println(configuration);
}
public static String removeGenerics(String className) {
int openingBracket = className.indexOf("<");
if (openingBracket == -1)
return className;
else
return className.substring(0, openingBracket);
}
public static boolean hasDeprecatedConstructor(String className) {
Class theClass = null;
try {
theClass = Class.forName(className);
} catch (Throwable e) {
// Ignore bad results
System.out.println("Skipping: " + className);
return false;
}
Annotation[] annotations = theClass.getAnnotations();
Optional<Annotation> deprecatedConstructor = Stream
.of(annotations)
.filter(annotation -> annotation.toString().equals(
"#java.lang.Deprecated()")).findAny();
return deprecatedConstructor.isPresent();
}
}
There is one problem with this approach though. You may want to use a deprecated class when a non-deprecated version does not exist. You will not see the deprecated class if it has been purposefully hidden. To resolve that, just be sure you exclude them from the filter.

Add code to package private library method

I have a library class with a package private method. Directly overriding this method by a subclass is no option. Is there any way, no matter how ugly, to execute own code when this package private method is called from inside the library, e.g. using AspectJ?
Here is a simplified example of the class (the packagePrivateMethod() actually is not invoked directly, but from native code):
public LibClass {
public LibClass() {
...
packagePrivateMethod();
...
}
void packagePrivateMethod() {
// <-- here I want to execute additional code
...
}
}
You could use a rather heavyweight approach.
Write a small Java agent SO post about that topic.
Use the provided Instrumentation interface to intercept the class loading
Use a byte code modification library (e.g. ASM or Java Assist (only Java 6 !) ) to instrument the byte code (e.g. to replace the method call with whatever you really want to do.
This would work as you can modify the byte code of everything, but it requires you to modify that byte code before it is executed.
Of course you can do that also statically by just modifying the class file, replacing the existing byte code with the byte code you create in step 3 above.
If you do not want / cannot statically replace the byte code of the class, you'll have to do the modification of the bytecode at runtime. For the using a Java agent is a good and solid idea.
Since this is all rather abstract until now, I have added an example which will intercept the loading of your library class, inject a method call in a package private method. When the main method executes, you can see from the output, that the injected method is called directly before the library classes' code. If you add return; as the injected code, you can also prevent the execution of that method alltogether.
So here is the code of an example to your problem solved with Java 6 and JavaAssist. If you want to go along that path and use something newer like Java 7, the you just have to replace the byte code manipulation with ASM. This is a little bit less readable, but also not exactly rocket science.
The main class:
package com.aop.example;
public class Main {
public static void main(String[] args) {
System.out.println("Main starts!");
LibClass libClass = new LibClass();
System.out.println("Main finished!");
}
}
Your LibClass:
package com.aop.example;
public class LibClass {
public LibClass() {
packagePrivateMethod();
}
void packagePrivateMethod() {
// <-- here I want to execute additional code
System.out.println("In packagePrivateMethod");
}
}
The Agent:
package com.aop.agent;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
public class Agent {
public static void premain(String agentArgs, Instrumentation instr) {
System.out.println("Agent starts!");
instr.addTransformer(new ClassFileTransformer() {
#Override
public byte[] transform(ClassLoader classLoader, String className, Class<?> arg2, ProtectionDomain arg3,
byte[] bytes)
throws IllegalClassFormatException {
System.out.println("Before loading class " + className);
final String TARGET_CLASS = "com/aop/example/LibClass";
if (!className.equals(TARGET_CLASS)) {
return null;
}
LoaderClassPath path = new LoaderClassPath(classLoader);
ClassPool pool = new ClassPool();
pool.appendSystemPath();
pool.appendClassPath(path);
try {
CtClass targetClass = pool.get(TARGET_CLASS.replace('/', '.'));
System.out.println("Enhancing class " + targetClass.getName());
CtMethod[] methods = targetClass.getDeclaredMethods();
for (CtMethod method : methods) {
if (!method.getName().contains("packagePrivateMethod")) {
continue;
}
System.out.println("Enhancing method " + method.getSignature());
String myMethodInvocation = "com.aop.agent.Agent.myMethodInvocation();";
method.insertBefore(myMethodInvocation);
}
System.out.println("Enhanced bytecode");
return targetClass.toBytecode();
}
catch (CannotCompileException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
catch (NotFoundException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
});
}
public static void myMethodInvocation() {
System.out.println("<<<My injected code>>>!");
}
}
The command for running the example (you have to put a agent in a jar with the manifest having an attribute Premain-Class: com.aop.agent.Agent:
%JAVA_HOME%\bin\java -cp .;..\javassist-3.12.1.GA.jar -javaagent:..\..\agent.jar com.aop.example.Main
The output of this example running a command like this:
Agent starts!
Before loading class com/aop/example/Main
Main starts!
Before loading class com/aop/example/LibClass
Enhancing class com.aop.example.LibClass
Enhancing method ()V
Enhanced bytecode
<<<My injected code>>>!
In packagePrivateMethod
Main finished!
Before loading class java/lang/Shutdown
Before loading class java/lang/Shutdown$Lock
You can you Mockito or similar mock library to mock a package private method. Example:
// declared in a package
public class Foo {
String foo(){
return "hey!";
}
}
#Test
public void testFoo() throws Exception {
Foo foo = Mockito.spy(new Foo());
Assert.assertEquals("hey!", foo.foo());
Mockito.when(foo.foo()).thenReturn("bar!");
Assert.assertEquals("bar!", foo.foo());
}
Can you add Spring to your project?
It might be possible to use a ProxyFactory - see another SO post
Using the ProxyFactory, you can add an advice for a class instance and delegate the method execution to another class (which does packagePrivateMethod() and/or replaces it with the code you want).
Since the library is not spring-managed, you might have to use load-time weaving with spring: ltw xml & examples
use the decorator pattern. Its specifically designed for this situation. If you need more details then ping me back else check this
Or you can also use reflections or a byte code manipulation mechanism to create your type dynamically at runtime.
Another idea: create a new class with the same name in the same package.
Say you want to replace LibraryClass in the below project:
Project structure:
- library.jar (contains com.example.LibraryClass)
- src
- com
- mycompany
- MyClass.java
Just create the package and file with the same name.
Project structure:
- library.jar (contains com.example.LibraryClass)
- src
- com
- mycompany
- MyClass.java
- example
- LibraryClass.java <- create this package and file
This relies on the class loader picking up your file instead of the library's file, but if you are just trying to get a hack working for testing, it is worth a shot. I'm not sure how the class loader decides which file to load, so this may not work in all environments.
If you don't have the source code for LibraryClass, just copy the decompiled code, and make your changes.
For the project where I needed this ability, it was just some test prototyping code... I didn't need anything production quality, or to work in all environments.

The method aspectOf is undefined

I built a mailserver in java and I have been providedwith an ObserverProtocol written with AOP. I am using Eclipse with AspectJ plugin as required
I try to use static methods like aspectOf, who I as understand should be added at the weaving step
Eclipse can't seem to make it work as I always get the following error :
Description Resource Path Location Type
The method aspectOf() is undefined for the type ObserverProtocol MailReaderBean.java /emailClent_test/src/emailserver line 86 Java Problem
From what I understood by snooping around various websites, tutorials and documentation, it seems that my .aj files aren't being woven correctly
I tried compiling manually with ajc to no avail, and I tinkered with paths and settings and jars and libs in various ways, nothing seems to work and I can't seem to find a definitive guide or tutorial to setup things correctly
My aspect code :
package protocol;
import java.util.WeakHashMap;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
public abstract aspect ObserverProtocol
{
protected interface Subject { }
protected interface Observer { }
private WeakHashMap perSubjectObservers;
protected List getObservers(Subject s)
{
if (perSubjectObservers == null)
{
perSubjectObservers = new WeakHashMap();
}
List observers = (List)perSubjectObservers.get(s);
if ( observers == null )
{
observers = new LinkedList();
perSubjectObservers.put(s, observers);
}
return observers;
}
public void addObserver(Subject s, Observer o)
{
getObservers(s).add(o);
}
public void removeObserver(Subject s, Observer o)
{
getObservers(s).remove(o);
}
protected abstract pointcut subjectChange(Subject s);
after(Subject s): subjectChange(s)
{
Iterator iter = getObservers(s).iterator();
while ( iter.hasNext() )
{
updateObserver(s, ((Observer)iter.next()));
}
}
protected abstract void updateObserver(Subject s, Observer o);
public static ObserverProtocol aspectOf() {
// TODO Auto-generated method stub
return this;
}
}
Offending code in my java server
//Add observer Proxy for monitoring the subject MailServer.
ObserverProtocol.aspectOf().addObserver(this, proxy );
Is there any resources that could help me understand how weaving works and how to setup my build without resorting to Spring or Maven?
This method (and hasAspect()) is added during weaving. If your aspect was built with javac rather than ajc, then it will not have these methods when jvm starts because the weaver hasn't yet run.
I recommend you read these documents:
Hello World (AspectJ)
Getting Started with AspectJ

FindBugs and CheckForNull on classes vs. interfaces

Is there any way to let FindBugs check and warn me if a CheckForNull annotation is present on the implementation of a method in a class, but not on the declaration of the method in the interface?
import javax.annotation.CheckForNull;
interface Foo {
public String getBar();
}
class FooImpl implements Foo {
#CheckForNull
#Override
public String getBar() {
return null;
}
}
public class FindBugsDemo {
public static void main(String[] args) {
Foo foo = new FooImpl();
System.out.println(foo.getBar().length());
}
}
I just discovered a bug in my application due to a missing null check that was not spotted by FindBugs because CheckForNull was only present on FooImpl, but not on Foo, and I don't want to spot all other locations of this problem manually.
Yes you can write your own detectors, and package them in your own jar plugin file. See fb-contrib.sf.net as an example of an auxillary findbugs plugin.
The one issue is that FindBugs relies on bcel 5.2 which doesn't have annotation support yet. You still get the attributes passed to you that represent the annotations, but it's more manual coding than will be the case in 5.3 (or whatever the next version of bcel is).

Categories

Resources