package org.checkerframework.framework.type.visitor;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeKind;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.javacutil.ErrorReporter;
/**
* Replaces or adds all the annotations in the parameter with the annotations from the visited type.
* An annotation is replaced if the parameter type already has an annotation in the same hierarchy
* at the same location as the visited type.
*
* <p>Example use:
*
* <pre>{@code
* AnnotatedTypeMirror visitType = ...;
* AnnotatedTypeMirror parameter = ...;
* visitType.accept(new AnnotatedTypesMerger(), parameter);
* }</pre>
*
* @author smillst
*/
public class AnnotatedTypeMerger extends AnnotatedTypeComparer<Void> {
public static void merge(final AnnotatedTypeMirror from, final AnnotatedTypeMirror to) {
if (from == to) {
ErrorReporter.errorAbort("From == to");
}
new AnnotatedTypeMerger().visit(from, to);
}
public static void merge(
final AnnotatedTypeMirror from,
final AnnotatedTypeMirror to,
final AnnotationMirror top) {
if (from == to) {
ErrorReporter.errorAbort("From == to");
}
new AnnotatedTypeMerger(top).visit(from, to);
}
// If top != null we replace only the annotations in the hierarchy of top.
private final AnnotationMirror top;
public AnnotatedTypeMerger() {
this.top = null;
}
/**
* @param top if top != null, then only annotation in the hierarchy of top are affected by this
* merger
*/
public AnnotatedTypeMerger(final AnnotationMirror top) {
this.top = top;
}
@Override
protected Void compare(AnnotatedTypeMirror one, AnnotatedTypeMirror two) {
if (one != null && two != null) {
replaceAnnotations(one, two);
}
return null;
}
@Override
protected Void combineRs(Void r1, Void r2) {
return r1;
}
protected void replaceAnnotations(
final AnnotatedTypeMirror one, final AnnotatedTypeMirror two) {
if (top == null) {
two.replaceAnnotations(one.getAnnotations());
} else {
final AnnotationMirror replacement = one.getAnnotationInHierarchy(top);
if (replacement != null) {
two.replaceAnnotation(one.getAnnotationInHierarchy(top));
}
}
}
@Override
public Void visitTypeVariable(AnnotatedTypeVariable from, AnnotatedTypeMirror to) {
resolvePrimaries(from, to);
return super.visitTypeVariable(from, to);
}
@Override
public Void visitWildcard(AnnotatedWildcardType from, AnnotatedTypeMirror to) {
resolvePrimaries(from, to);
return super.visitWildcard(from, to);
}
/**
* For type variables and wildcards, the absence of a primary annotations has an implied meaning
* on substitution. Therefore, in these cases we remove the primary annotation and rely on the
* fact that the bounds are also merged into the type to.
*
* @param from a type variable or wildcard
*/
public void resolvePrimaries(AnnotatedTypeMirror from, AnnotatedTypeMirror to) {
if (from.getKind() == TypeKind.WILDCARD || from.getKind() == TypeKind.TYPEVAR) {
if (top != null) {
if (from.getAnnotationInHierarchy(top) == null) {
to.removeAnnotationInHierarchy(top);
}
} else {
for (final AnnotationMirror toPrimaryAnno : to.getAnnotations()) {
if (from.getAnnotationInHierarchy(toPrimaryAnno) == null) {
to.removeAnnotation(toPrimaryAnno);
}
}
}
} else {
ErrorReporter.errorAbort(
"ResolvePrimaries' from argument should be a type variable OR wildcard\n"
+ "from="
+ from.toString(true)
+ "\n"
+ "to="
+ to.toString(true));
}
}
}