package org.checkerframework.framework.type.typeannotator; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.javacutil.CollectionUtils; import org.checkerframework.javacutil.TypesUtils; /** * Adds annotations to types that are not relevant specified by the {@link RelevantJavaTypes} on a * checker. */ public class IrrelevantTypeAnnotator extends TypeAnnotator { /** * List of relevantTypes translated from classes in {@link RelevantJavaTypes} to {@link * TypeMirror}s */ private List<TypeMirror> relevantTypes; /** * Cache of types found that are relevantTypes or subclass of supported types. Used so that * isSubtype doesn't need to be called repeatedly on the same types. */ private Set<TypeMirror> allFoundRelevantTypes; private boolean arraysAreRelevant; private Set<? extends AnnotationMirror> annotations; /** * Annotate every type with the annotationMirror except for those whose underlying Java type is * one of (or a subtype of) a class in relevantClasses. (Only adds annotationMirror if no * annotation in the hierarchy are already on the type.) If relevantClasses includes * Object[].class, then all arrays are considered relevant. * * @param typeFactory AnnotatedTypeFactory * @param annotations annotations to add * @param relevantClasses types that should not be annotated with annotationMirror */ public IrrelevantTypeAnnotator( AnnotatedTypeFactory typeFactory, Set<? extends AnnotationMirror> annotations, Class<?>[] relevantClasses) { super(typeFactory); this.annotations = annotations; this.arraysAreRelevant = false; this.relevantTypes = new ArrayList<>(relevantClasses.length); for (Class<?> clazz : relevantClasses) { if (clazz.equals(Object[].class)) { arraysAreRelevant = true; } else { relevantTypes.add( TypesUtils.typeFromClass( typeFactory.getContext().getTypeUtils(), typeFactory.getElementUtils(), clazz)); } } this.allFoundRelevantTypes = Collections.newSetFromMap(CollectionUtils.<TypeMirror, Boolean>createLRUCache(300)); } @Override protected Void scan(AnnotatedTypeMirror type, Void aVoid) { if (type == null) { return aVoid; } switch (type.getKind()) { case TYPEVAR: case WILDCARD: case EXECUTABLE: case INTERSECTION: case UNION: case NULL: case NONE: case PACKAGE: case VOID: return super.scan(type, aVoid); default: // go on } Types types = typeFactory.getContext().getTypeUtils(); TypeMirror typeMirror = type.getUnderlyingType(); if (TypesUtils.isPrimitive(typeMirror)) { typeMirror = types.boxedClass((PrimitiveType) typeMirror).asType(); } boolean shouldAnnotate = true; if (allFoundRelevantTypes.contains(typeMirror)) { shouldAnnotate = false; } else if (typeMirror.getKind() == TypeKind.DECLARED) { for (TypeMirror supportedType : relevantTypes) { if (types.isSubtype(typeMirror, supportedType)) { shouldAnnotate = false; allFoundRelevantTypes.add(typeMirror); break; } } } else if (typeMirror.getKind() == TypeKind.ARRAY) { shouldAnnotate = arraysAreRelevant; if (arraysAreRelevant) { allFoundRelevantTypes.add(typeMirror); } } if (shouldAnnotate) { type.addMissingAnnotations(annotations); } return super.scan(type, aVoid); } @Override public Void visitExecutable(AnnotatedExecutableType t, Void p) { // super skips the receiver scan(t.getReturnType(), p); scanAndReduce(t.getReceiverType(), p, null); scanAndReduce(t.getParameterTypes(), p, null); scanAndReduce(t.getThrownTypes(), p, null); scanAndReduce(t.getTypeVariables(), p, null); return null; } }