package org.checkerframework.checker.index.searchindex; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.index.IndexUtil; import org.checkerframework.checker.index.qual.NegativeIndexFor; import org.checkerframework.checker.index.qual.SearchIndexBottom; import org.checkerframework.checker.index.qual.SearchIndexFor; import org.checkerframework.checker.index.qual.SearchIndexUnknown; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.value.ValueAnnotatedTypeFactory; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.util.AnnotationBuilder; import org.checkerframework.framework.util.MultiGraphQualifierHierarchy; import org.checkerframework.javacutil.AnnotationUtils; /** * The Search Index Checker is used to help type the results of calls to the JDK's binary search * methods. It is part of the Index Checker. */ public class SearchIndexAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { public final AnnotationMirror UNKNOWN, BOTTOM; public SearchIndexAnnotatedTypeFactory(BaseTypeChecker checker) { super(checker); UNKNOWN = AnnotationUtils.fromClass(elements, SearchIndexUnknown.class); BOTTOM = AnnotationUtils.fromClass(elements, SearchIndexBottom.class); this.postInit(); } /** * Provides a way to query the Constant Value Checker, which computes the values of expressions * known at compile time (constant propagation and folding). */ ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { return getTypeFactoryOfSubchecker(ValueChecker.class); } @Override protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() { return new LinkedHashSet<>( Arrays.asList( SearchIndexFor.class, SearchIndexBottom.class, SearchIndexUnknown.class, NegativeIndexFor.class)); } @Override public QualifierHierarchy createQualifierHierarchy( MultiGraphQualifierHierarchy.MultiGraphFactory factory) { return new SearchIndexQualifierHierarchy(factory); } private final class SearchIndexQualifierHierarchy extends MultiGraphQualifierHierarchy { public SearchIndexQualifierHierarchy( MultiGraphQualifierHierarchy.MultiGraphFactory factory) { super(factory); } @Override public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) { if (AnnotationUtils.areSame(a1, UNKNOWN)) { return a2; } if (AnnotationUtils.areSame(a2, UNKNOWN)) { return a1; } if (AnnotationUtils.areSame(a1, BOTTOM)) { return a1; } if (AnnotationUtils.areSame(a2, BOTTOM)) { return a2; } if (isSubtype(a1, a2)) { return a1; } if (isSubtype(a2, a1)) { return a2; } // If neither is a subtype of the other, then create an // annotation that combines their values. // Each annotation is either NegativeIndexFor or SearchIndexFor. Set<String> combinedArrays = new HashSet<>(IndexUtil.getValueOfAnnotationWithStringArgument(a1)); combinedArrays.addAll(IndexUtil.getValueOfAnnotationWithStringArgument(a2)); if (AnnotationUtils.areSameByClass(a1, NegativeIndexFor.class) || AnnotationUtils.areSameByClass(a2, NegativeIndexFor.class)) { return createNegativeIndexFor(Arrays.asList(combinedArrays.toArray(new String[0]))); } else { return createSearchIndexFor(Arrays.asList(combinedArrays.toArray(new String[0]))); } } @Override public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) { if (AnnotationUtils.areSame(a1, UNKNOWN)) { return a1; } if (AnnotationUtils.areSame(a2, UNKNOWN)) { return a2; } if (AnnotationUtils.areSame(a1, BOTTOM)) { return a2; } if (AnnotationUtils.areSame(a2, BOTTOM)) { return a1; } if (isSubtype(a1, a2)) { return a2; } if (isSubtype(a2, a1)) { return a1; } // If neither is a subtype of the other, then create an // annotation that includes only their overlapping values. // Each annotation is either NegativeIndexFor or SearchIndexFor. List<String> arrayIntersection = IndexUtil.getValueOfAnnotationWithStringArgument(a1); arrayIntersection.retainAll(IndexUtil.getValueOfAnnotationWithStringArgument(a2)); if (arrayIntersection.size() == 0) { return UNKNOWN; } if (AnnotationUtils.areSameByClass(a1, SearchIndexFor.class) || AnnotationUtils.areSameByClass(a2, SearchIndexFor.class)) { return createSearchIndexFor( Arrays.asList(arrayIntersection.toArray(new String[0]))); } else { return createNegativeIndexFor( Arrays.asList(arrayIntersection.toArray(new String[0]))); } } @Override public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { if (AnnotationUtils.areSameByClass(superAnno, SearchIndexUnknown.class)) { return true; } if (AnnotationUtils.areSameByClass(subAnno, SearchIndexBottom.class)) { return true; } if (AnnotationUtils.areSameByClass(subAnno, SearchIndexUnknown.class)) { return false; } if (AnnotationUtils.areSameByClass(superAnno, SearchIndexBottom.class)) { return false; } // Each annotation is either NegativeIndexFor or SearchIndexFor. List<String> superArrays = IndexUtil.getValueOfAnnotationWithStringArgument(superAnno); List<String> subArrays = IndexUtil.getValueOfAnnotationWithStringArgument(subAnno); // Subtyping requires: // * subtype is NegativeIndexFor or supertype is SearchIndexFor // * subtype's arrays are a superset of supertype's arrays return ((AnnotationUtils.areSameByClass(subAnno, NegativeIndexFor.class) || AnnotationUtils.areSameByClass(superAnno, SearchIndexFor.class)) && subArrays.containsAll(superArrays)); } } /** Create a new {@code @NegativeIndexFor} annotation with the given arrays as its arguments. */ AnnotationMirror createNegativeIndexFor(List<String> arrays) { if (arrays.size() == 0) { return UNKNOWN; } arrays = new ArrayList<>(new HashSet<>(arrays)); // remove duplicates Collections.sort(arrays); AnnotationBuilder builder = new AnnotationBuilder(processingEnv, NegativeIndexFor.class); builder.setValue("value", arrays); return builder.build(); } /** Create a new {@code @SearchIndexFor} annotation with the given arrays as its arguments. */ AnnotationMirror createSearchIndexFor(List<String> arrays) { if (arrays.size() == 0) { return UNKNOWN; } arrays = new ArrayList<>(new HashSet<>(arrays)); // remove duplicates Collections.sort(arrays); AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SearchIndexFor.class); builder.setValue("value", arrays); return builder.build(); } }