Due to a specific reason, I would like to use Checker Framework and its subtyping checker.
To make this checker work I have to use ElementType.TYPE_PARAMETER
and ElementType.TYPE_USE
.
However, I would like to remove them from local variables before compilation to class files.
For example, let's say I have the following code with custom @FirstName
and @LastName
(both must retain at the class level with RetentionPolicy.CLASS
):
@FirstName String firstName = ...;
@LastName String lastName = ...;
...
firstName = lastName; // illegal, the error is generated by Checker Framework because the first name cannot be assigned to the last name
but for another reason, I would like to remove the annotations from the local variables "at" bytecode level as if the source code is just:
String firstName = ...;
String lastName = ...;
...
firstName = lastName; // totally fine and legal in Java
If I understand the way it can be accomplished, annotation processing is a way to go. So, if it's a right thing to do, then I'd have to chain some annotation processors in the following order:
org.checkerframework.common.subtyping.SubtypingChecker
.- my custom "remove local variables annotations" annotation processor;
Well, diving into how javac
works is an extreme challenge to me.
What I have implemented so far is:
@SupportedOptions(RemoveLocalVariableAnnotationsProcessor.ANNOTATIONS_OPTION)
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public final class RemoveLocalVariableAnnotationsProcessor
extends AbstractProcessor {
private static final Pattern commaPattern = Pattern.compile(",");
static final String ANNOTATIONS_OPTION = "RemoveLocalVariableAnnotationsProcessor.annotations";
@Nonnull
private Predicate<? super Class<? extends Annotation>> annotationClasses = clazz -> false;
@Override
public void init(@Nonnull final ProcessingEnvironment environment) {
super.init(environment);
final Messager messager = environment.getMessager();
final Map<String, String> options = environment.getOptions();
@Nullable
final String annotationsOption = options.get(ANNOTATIONS_OPTION);
if ( annotationsOption != null ) {
annotationClasses = commaPattern.splitAsStream(annotationsOption)
.<Class<? extends Annotation>>flatMap(className -> {
try {
@SuppressWarnings("unchecked")
final Class<? extends Annotation> clazz = (Class<? extends Annotation>) Class.forName(className);
if ( !clazz.isAnnotation() ) {
messager.printMessage(Diagnostic.Kind.WARNING, "Not an annotation: " + className);
return Stream.empty();
}
return Stream.of(clazz);
} catch ( final ClassNotFoundException ex ) {
messager.printMessage(Diagnostic.Kind.WARNING, "Cannot find " + className);
return Stream.empty();
}
})
.collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet))
::contains;
}
final Trees trees = Trees.instance(environment);
final JavacTask javacTask = JavacTask.instance(environment);
javacTask.addTaskListener(new RemoverTaskListener(trees, messager));
}
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment environment) {
// do nothing: ElementType.TYPE_USE and ElementType.TYPE_PARAMETER seem to be unable to be analyzed here
return false;
}
private static final class RemoverTaskListener
implements TaskListener {
private final Trees trees;
private final Messager messager;
private RemoverTaskListener(final Trees trees, final Messager messager) {
this.trees = trees;
this.messager = messager;
}
@Override
public void started(final TaskEvent taskEvent) {
if ( taskEvent.getKind() == TaskEvent.Kind.ANALYZE ) {
final TreeScanner<?, ?> remover = new Remover(trees, messager);
remover.scan(taskEvent.getCompilationUnit(), null);
}
}
@Override
public void finished(final TaskEvent taskEvent) {
// do nothing
}
private static final class Remover
extends TreePathScanner<Void, Void> {
private final Trees trees;
private final Messager messager;
private Remover(final Trees trees, final Messager messager) {
this.trees = trees;
this.messager = messager;
}
@Override
public Void visitVariable(final VariableTree variableTree, final Void nothing) {
super.visitVariable(variableTree, nothing);
final Symbol symbol = (Symbol) trees.getElement(trees.getPath(getCurrentPath().getCompilationUnit(), variableTree));
if ( !symbol.hasTypeAnnotations() || symbol.getKind() != ElementKind.LOCAL_VARIABLE ) {
return nothing;
}
final List<? extends AnnotationTree> annotationTrees = variableTree.getModifiers().getAnnotations();
if ( annotationTrees.isEmpty() ) {
return nothing;
}
messager.printMessage(Diagnostic.Kind.WARNING, "TODO: " + symbol);
for ( final AnnotationTree annotationTree : annotationTrees ) {
// TODO how to align AnnotationTree and java.lang.annotation.Annotation?
// TODO how to remove the annotation from the local variable?
}
return nothing;
}
}
}
}
As you can see, it does not work as it's supposed to do.
What is a proper way of removing the annotations from local variables?
I mean, how do I accomplish it?
If it's possible, I would like to stick to javac
annotation processors due to the Maven build integration specifics.