Skip to content

Commit e0a6095

Browse files
implement Covariant and Contravariant annotation, and impose strict if specified
1 parent f4ad9be commit e0a6095

File tree

4 files changed

+89
-26
lines changed

4 files changed

+89
-26
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
<artifactId>auto-service</artifactId>
4444
<version>1.0-rc5</version>
4545
</dependency>
46+
<dependency>
47+
<groupId>io.leangen.geantyref</groupId>
48+
<artifactId>geantyref</artifactId>
49+
<version>2.0.0</version>
50+
</dependency>
4651
</dependencies>
4752

4853

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package anthonisen.felix.annotationProcessing;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.SOURCE)
9+
@Target(ElementType.TYPE_PARAMETER)
10+
public @interface Contravariant {
11+
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package anthonisen.felix.annotationProcessing;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.SOURCE)
9+
@Target(ElementType.TYPE_PARAMETER)
10+
public @interface Covariant {
11+
12+
}

src/main/java/anthonisen/felix/annotationProcessing/VarianceProcessor.java

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22

33
import com.github.javaparser.ast.CompilationUnit;
44
import com.github.javaparser.ast.type.Type;
5+
6+
import java.lang.annotation.Annotation;
7+
8+
import io.leangen.geantyref.AnnotationFormatException;
9+
import io.leangen.geantyref.TypeFactory;
510
import java.util.HashSet;
11+
import java.util.Map;
612
import java.util.Set;
713
import javax.annotation.processing.AbstractProcessor;
814
import javax.annotation.processing.Messager;
@@ -16,70 +22,98 @@
1622
import javax.lang.model.element.TypeParameterElement;
1723
import javax.tools.Diagnostic.Kind;
1824
import com.google.auto.service.AutoService;
25+
import com.google.common.collect.ImmutableList;
26+
1927
import anthonisen.felix.astParsing.AstManipulator;
2028
import anthonisen.felix.astParsing.util.TypeHandler;
2129
import anthonisen.felix.astParsing.visitors.ParameterTypeCollector;
2230
import anthonisen.felix.astParsing.visitors.ReturnTypeCollector;
2331

2432
@AutoService(Processor.class)
2533
@SupportedSourceVersion(SourceVersion.RELEASE_17)
26-
@SupportedAnnotationTypes("anthonisen.felix.annotationProcessing.MyVariance")
34+
@SupportedAnnotationTypes({
35+
"anthonisen.felix.annotationProcessing.MyVariance",
36+
"anthonisen.felix.annotationProcessing.Covariant",
37+
"anthonisen.felix.annotationProcessing.Contravariant",
38+
})
2739
public class VarianceProcessor extends AbstractProcessor {
2840
private Messager messager;
2941
private AstManipulator astManipulator;
42+
private final ImmutableList<Class<? extends Annotation>> supportedAnnotations = ImmutableList.of(MyVariance.class,
43+
Covariant.class, Contravariant.class);
3044

3145
@Override
3246
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
3347
messager = processingEnv.getMessager();
3448
astManipulator = new AstManipulator(messager,
3549
System.getProperty("user.dir") + "/src/main/java");
3650
messager.printMessage(Kind.NOTE, "Processing annotations:\n");
37-
for (Element e : roundEnv.getElementsAnnotatedWith(MyVariance.class)) {
38-
MyVariance annotation = e.getAnnotation(MyVariance.class);
39-
// should not process method declarations
40-
if (!isClassParameter(e))
41-
continue;
51+
for (Class<? extends Annotation> annotationType : supportedAnnotations) {
52+
for (Element e : roundEnv.getElementsAnnotatedWith(annotationType)) {
53+
MyVariance annotation = e.getAnnotation(MyVariance.class);
54+
try {
55+
if (annotationType.equals(Covariant.class))
56+
annotation = TypeFactory.annotation(MyVariance.class,
57+
Map.of("variance", VarianceType.COVARIANT, "strict", true));
58+
else if (annotationType.equals(Contravariant.class))
59+
annotation = TypeFactory.annotation(MyVariance.class,
60+
Map.of("variance", VarianceType.CONTRAVARIANT, "strict", true));
4261

43-
TypeParameterElement tE = (TypeParameterElement) e;
44-
String className = tE.getEnclosingElement().getSimpleName().toString();
45-
String packageName = processingEnv.getElementUtils().getPackageOf(tE.getEnclosingElement()).toString();
62+
} catch (AnnotationFormatException ex) {
63+
// catch this later
64+
}
65+
if (annotation != null)
66+
processElement(annotation, e);
67+
else
68+
messager.printMessage(Kind.WARNING, "Could not parse annotation for element: " + e);
69+
}
70+
}
71+
astManipulator.applyChanges();
72+
return true;
73+
}
4674

47-
if (packageName.contains("output"))
48-
continue;
75+
private void processElement(MyVariance annotation, Element e) {
76+
// should not process method declarations
77+
if (!isClassParameter(e))
78+
return;
4979

50-
if (annotation.variance() == VarianceType.INVARIANT) {
51-
messager.printMessage(Kind.NOTE,
52-
String.format(
53-
"Invariant type parameter detected in class: %s\nWill not proceed with AST manipulation",
54-
className));
55-
}
80+
TypeParameterElement tE = (TypeParameterElement) e;
81+
String className = tE.getEnclosingElement().getSimpleName().toString();
82+
String packageName = processingEnv.getElementUtils().getPackageOf(tE.getEnclosingElement()).toString();
5683

57-
checkVariance(className, annotation.variance(), packageName, tE.getSimpleName().toString());
58-
astManipulator.eraseTypesAndInsertCasts(className + ".java", packageName, tE.getSimpleName().toString());
84+
if (packageName.contains("output"))
85+
return;
5986

87+
if (annotation.variance() == VarianceType.INVARIANT) {
88+
messager.printMessage(Kind.NOTE,
89+
String.format(
90+
"Invariant type parameter detected in class: %s\nWill not proceed with AST manipulation",
91+
className));
6092
}
61-
astManipulator.applyChanges();
62-
return true;
93+
94+
checkVariance(className, annotation, packageName, tE.getSimpleName().toString());
95+
astManipulator.eraseTypesAndInsertCasts(className + ".java", packageName,
96+
tE.getSimpleName().toString());
6397
}
6498

65-
private void checkVariance(String className, VarianceType variance, String packageName, String typeOfInterest) {
99+
private void checkVariance(String className, MyVariance annotation, String packageName, String typeOfInterest) {
66100
Set<Type> types = new HashSet<>();
67101
CompilationUnit cu = astManipulator.getSourceRoot().parse(packageName, className
68102
+ ".java");
69-
if (variance == VarianceType.CONTRAVARIANT)
103+
if (annotation.variance() == VarianceType.CONTRAVARIANT)
70104
cu.accept(new ReturnTypeCollector(), types);
71105
else
72106
cu.accept(new ParameterTypeCollector(), types);
73107

74108
for (Type type : types) {
75109
if (TypeHandler.containsType(type, typeOfInterest)) {
76110
messager.printMessage(
77-
Kind.WARNING,
111+
annotation.strict() ? Kind.ERROR : Kind.WARNING,
78112
String.format(
79113
"%s is declared as %s, but does not conform to constraints: contains T in %s position",
80114
className,
81-
variance,
82-
variance == VarianceType.COVARIANT ? "IN" : "OUT"));
115+
annotation.variance(),
116+
annotation.variance() == VarianceType.COVARIANT ? "IN" : "OUT"));
83117
break;
84118
}
85119
}

0 commit comments

Comments
 (0)