| package org.checkerframework.framework.util.element; |
| |
| import com.sun.tools.javac.code.Attribute; |
| import com.sun.tools.javac.code.Attribute.TypeCompound; |
| import com.sun.tools.javac.code.TargetType; |
| import com.sun.tools.javac.code.Type; |
| import com.sun.tools.javac.code.TypeAnnotationPosition; |
| import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry; |
| import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.IdentityHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeVariable; |
| import org.checkerframework.checker.formatter.qual.FormatMethod; |
| import org.checkerframework.framework.type.AnnotatedTypeFactory; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; |
| import org.checkerframework.framework.type.ElementAnnotationApplier; |
| import org.checkerframework.framework.util.AnnotatedTypes; |
| import org.checkerframework.javacutil.AnnotationUtils; |
| import org.checkerframework.javacutil.BugInCF; |
| import org.checkerframework.javacutil.TypesUtils; |
| import org.plumelib.util.StringsPlume; |
| |
| /** |
| * Utility methods for adding the annotations that are stored in an Element to the type that |
| * represents that element (or a use of that Element). This class also contains package private |
| * methods used by the ElementAnnotationAppliers that do most of the work. |
| */ |
| public class ElementAnnotationUtil { |
| |
| /** |
| * For each type/element pair, add all of the annotations stored in Element to type. See apply for |
| * more details. |
| * |
| * @param types the types to which we wish to apply element annotations |
| * @param elements the elements that may contain annotations to apply. elements.size must == |
| * types.size |
| * @param typeFactory the type factory used to create the AnnotatedTypeMirrors contained by types |
| */ |
| public static void applyAllElementAnnotations( |
| final List<? extends AnnotatedTypeMirror> types, |
| final List<? extends Element> elements, |
| final AnnotatedTypeFactory typeFactory) { |
| |
| if (types.size() != elements.size()) { |
| throw new BugInCF( |
| "Number of types and elements don't match. " |
| + "types ( " |
| + StringsPlume.join(", ", types) |
| + " ) " |
| + "element ( " |
| + StringsPlume.join(", ", elements) |
| + " ) "); |
| } |
| |
| for (int i = 0; i < types.size(); i++) { |
| ElementAnnotationApplier.apply(types.get(i), elements.get(i), typeFactory); |
| } |
| } |
| |
| /** |
| * When a declaration annotation is an alias for a type annotation, then the Checker Framework may |
| * move the annotation before replacing it by the canonical version. |
| * |
| * <p>If the annotation is one of the Checker Framework compatibility annotations, for example |
| * org.checkerframework.checker.nullness.compatqual.NonNullDecl, then it is interpreted as a type |
| * annotation in the same location. |
| * |
| * @param type the type to annotate |
| * @param annotations the annotations to add |
| */ |
| @SuppressWarnings("interning:not.interned") // AST node comparison |
| static void addDeclarationAnnotationsFromElement( |
| final AnnotatedTypeMirror type, final List<? extends AnnotationMirror> annotations) { |
| // The code here should be similar to |
| // org.checkerframework.framework.type.TypeFromMemberVisitor.visitVariable |
| AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(type); |
| if (innerType != type) { |
| for (AnnotationMirror annotation : annotations) { |
| if (AnnotationUtils.annotationName(annotation).startsWith("org.checkerframework")) { |
| innerType.addAnnotation(annotation); |
| } else { |
| type.addAnnotation(annotation); |
| } |
| } |
| } else { |
| type.addAnnotations(annotations); |
| } |
| } |
| |
| /** |
| * Does expectedValues contain enumValue. This is just a linear search. |
| * |
| * @param enumValue value to search for, a needle |
| * @param expectedValues values to search through, a haystack |
| * @return true if enumValue is in expectedValues, false otherwise |
| */ |
| static boolean contains(Object enumValue, Object[] expectedValues) { |
| for (final Object expected : expectedValues) { |
| if (enumValue.equals(expected)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Use a map to partition annotations with the given TargetTypes into Lists, where each target |
| * type is a key in the output map. Any annotation that does not have one of these target types |
| * will be added to unmatched |
| * |
| * @param annos the collection of annotations to partition |
| * @param unmatched a list to add annotations with unmatched target types to |
| * @param targetTypes a list of target types to partition annos with |
| * @return a map from targetType → List of Annotations that have that targetType |
| */ |
| static Map<TargetType, List<TypeCompound>> partitionByTargetType( |
| Collection<TypeCompound> annos, List<TypeCompound> unmatched, TargetType... targetTypes) { |
| final Map<TargetType, List<TypeCompound>> targetTypeToAnnos = new HashMap<>(); |
| for (TargetType targetType : targetTypes) { |
| targetTypeToAnnos.put(targetType, new ArrayList<>()); |
| } |
| |
| for (final TypeCompound anno : annos) { |
| final List<TypeCompound> annoSet = targetTypeToAnnos.get(anno.getPosition().type); |
| if (annoSet != null) { |
| annoSet.add(anno); |
| } else if (unmatched != null) { |
| unmatched.add(anno); |
| } |
| } |
| |
| return targetTypeToAnnos; |
| } |
| |
| /** |
| * A class used solely to annotate wildcards from Element annotations. Instances of |
| * WildcardBoundAnnos are used to aggregate ALL annotations for a given Wildcard and then apply |
| * them all at once in order to resolve the annotations in front of unbound wildcards. |
| * |
| * <p>Wildcard annotations are applied as follows: |
| * |
| * <ul> |
| * <li>a) If an Annotation is in front of a extends or super bounded wildcard, it applies to the |
| * bound that is NOT explicitly present. e.g. |
| * <pre>{@code |
| * <@A ? extends Object> -- @A is placed on the super bound (Void) |
| * <@B ? super CharSequence> -- @B is placed on the extends bound (probably Object) |
| * }</pre> |
| * <li>b) If an Annotation is on a bound, it applies to that bound. E.g. |
| * <pre>{@code |
| * <? extends @A Object> -- @A is placed on the extends bound (Object) |
| * <? super @B CharSequence> -- @B is placed on the super bound (CharSequence) |
| * }</pre> |
| * <li>c) If an Annotation is on an unbounded wildcard there are two subcases. |
| * <ul> |
| * <li>c.1 The user wrote the annotation explicitly -- these annotations apply to both |
| * bounds e.g. the user wrote |
| * <pre>{@code |
| * <@C ?> -- the annotation is placed on the extends/super bounds |
| * }</pre> |
| * <li>c.2 Previous calls to getAnnotatedType have annotated this wildcard with BOTH |
| * bounds e.g. the user wrote {@code <?>} but the checker framework added {@code <@C ? |
| * extends @D Object>} to the corresponding element. |
| * <pre> |
| * {@code <?> -- @C is placed on the lower bound and @D is placed on the upper bound |
| * This case is treated just like annotations in cases a/b. |
| * }</pre> |
| * </ul> |
| * </ul> |
| */ |
| private static final class WildcardBoundAnnos { |
| public final AnnotatedWildcardType wildcard; |
| public final Set<AnnotationMirror> upperBoundAnnos; |
| public final Set<AnnotationMirror> lowerBoundAnnos; |
| |
| // indicates that this is an annotation in front of an unbounded wildcard |
| // e.g. < @A ? > |
| // For each annotation in this set, if there is no annotation in upperBoundAnnos |
| // that is in the same hierarchy then the annotation will be applied to both bounds |
| // otherwise the annotation applies to the lower bound only |
| public final Set<AnnotationMirror> possiblyBoth; |
| |
| /** Whether or not wildcard has an explicit super bound. */ |
| private final boolean isSuperBounded; |
| |
| /** Whether or not wildcard has NO explicit bound whatsoever. */ |
| private final boolean isUnbounded; |
| |
| WildcardBoundAnnos(AnnotatedWildcardType wildcard) { |
| this.wildcard = wildcard; |
| this.upperBoundAnnos = AnnotationUtils.createAnnotationSet(); |
| this.lowerBoundAnnos = AnnotationUtils.createAnnotationSet(); |
| this.possiblyBoth = AnnotationUtils.createAnnotationSet(); |
| |
| this.isSuperBounded = AnnotatedTypes.hasExplicitSuperBound(wildcard); |
| this.isUnbounded = AnnotatedTypes.hasNoExplicitBound(wildcard); |
| } |
| |
| void addAnnotation(final TypeCompound anno) { |
| // if the typepath entry ends in Wildcard then the annotation should go on a bound |
| // otherwise, the annotation is in front of the wildcard |
| // e.g. @HERE ? extends Object |
| final boolean isInFrontOfWildcard = |
| anno.getPosition().location.last() != TypePathEntry.WILDCARD; |
| if (isInFrontOfWildcard && isUnbounded) { |
| possiblyBoth.add(anno); |
| |
| } else { |
| // A TypePathEntry of WILDCARD indicates that it is placed on the bound |
| // use the type of the wildcard bound to determine which set to put it in |
| |
| if (isInFrontOfWildcard) { |
| if (isSuperBounded) { |
| upperBoundAnnos.add(anno); |
| } else { |
| lowerBoundAnnos.add(anno); |
| } |
| } else { // it's on the bound |
| if (isSuperBounded) { |
| lowerBoundAnnos.add(anno); |
| } else { |
| upperBoundAnnos.add(anno); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Apply the annotations to wildcard according to the rules outlined in the comment at the |
| * beginning of this class. |
| */ |
| void apply() { |
| final AnnotatedTypeMirror extendsBound = wildcard.getExtendsBound(); |
| final AnnotatedTypeMirror superBound = wildcard.getSuperBound(); |
| |
| for (AnnotationMirror extAnno : upperBoundAnnos) { |
| extendsBound.addAnnotation(extAnno); |
| } |
| for (AnnotationMirror supAnno : lowerBoundAnnos) { |
| superBound.addAnnotation(supAnno); |
| } |
| |
| for (AnnotationMirror anno : possiblyBoth) { |
| superBound.addAnnotation(anno); |
| |
| // This will be false if we've defaulted the bounds and are reading them again. |
| // In that case, we will have already created an annotation for the extends bound |
| // that should be honored and NOT overwritten. |
| if (!extendsBound.isAnnotatedInHierarchy(anno)) { |
| extendsBound.addAnnotation(anno); |
| } |
| } |
| } |
| } |
| |
| /** |
| * TypeCompounds are implementations of AnnotationMirror that are stored on Elements. Each type |
| * compound has a TypeAnnotationPosition which identifies, relative to the "root" of a type, where |
| * an annotation should be placed. This method adds all of the given TypeCompounds to the correct |
| * location on type by interpreting the TypeAnnotationPosition. |
| * |
| * <p>Note: We handle all of the Element annotations on a type at once because we need to identify |
| * whether or not the element annotation in front of an unbound wildcard (e.g. {@code <@HERE ?>}) |
| * should apply to only the super bound or both the super bound and the extends bound. |
| * |
| * @see org.checkerframework.framework.util.element.ElementAnnotationUtil.WildcardBoundAnnos |
| * @param type the type in which annos should be placed |
| * @param annos all of the element annotations, TypeCompounds, for type |
| */ |
| static void annotateViaTypeAnnoPosition( |
| final AnnotatedTypeMirror type, final Collection<TypeCompound> annos) |
| throws UnexpectedAnnotationLocationException { |
| final IdentityHashMap<AnnotatedWildcardType, WildcardBoundAnnos> wildcardToAnnos = |
| new IdentityHashMap<>(); |
| for (final TypeCompound anno : annos) { |
| AnnotatedTypeMirror target = getTypeAtLocation(type, anno.position.location, anno, false); |
| if (target.getKind() == TypeKind.WILDCARD) { |
| addWildcardToBoundMap((AnnotatedWildcardType) target, anno, wildcardToAnnos); |
| } else { |
| target.addAnnotation(anno); |
| } |
| } |
| |
| for (WildcardBoundAnnos wildcardAnnos : wildcardToAnnos.values()) { |
| wildcardAnnos.apply(); |
| } |
| } |
| |
| /** |
| * Creates an entry in wildcardToAnnos for wildcard if one does not already exists. Adds anno to |
| * the WildcardBoundAnnos object for wildcard. |
| */ |
| private static void addWildcardToBoundMap( |
| final AnnotatedWildcardType wildcard, |
| final TypeCompound anno, |
| final Map<AnnotatedWildcardType, WildcardBoundAnnos> wildcardToAnnos) { |
| WildcardBoundAnnos boundAnnos = |
| wildcardToAnnos.computeIfAbsent(wildcard, WildcardBoundAnnos::new); |
| boundAnnos.addAnnotation(anno); |
| } |
| |
| /** |
| * Returns true if the typeCompound is a primary annotation for the type it targets (or lower |
| * bound if this is a type variable or wildcard ). If you think of a type as a tree-like structure |
| * then a nested type any type that is not the root. E.g. {@code @T List< @N String>}, @T is on a |
| * top-level NON-nested type where as the annotation @N is on a nested type. |
| * |
| * @param typeCompound the type compound to inspect |
| * @return true if typeCompound is placed on a nested type, false otherwise |
| */ |
| static boolean isOnComponentType(final Attribute.TypeCompound typeCompound) { |
| return !typeCompound.position.location.isEmpty(); |
| } |
| |
| /** |
| * See the Type Annotation Specification on bounds |
| * (https://checkerframework.org/jsr308/specification/java-annotation-design.html). |
| * |
| * <p>TypeAnnotationPositions have bound indices when they represent an upper bound on a |
| * TypeVariable. The index 0 ALWAYS refers to the superclass type. If that supertype is implied to |
| * be Object (because we didn't specify an extends) then the actual types will be offset by 1 |
| * (because index 0 is ALWAYS a class. |
| * |
| * <p>Therefore, These indices will be offset by -1 if the first type in the bound is an interface |
| * which implies the specified type itself is an interface. |
| * |
| * <p>Reminder: There will only be multiple bound types if the upperBound is an intersection. |
| * |
| * @param upperBoundTypes the list of upperBounds for the type with bound positions you wish to |
| * offset |
| * @return the bound offset for all TypeAnnotationPositions of TypeCompounds targeting these |
| * bounds |
| */ |
| static int getBoundIndexOffset(final List<? extends AnnotatedTypeMirror> upperBoundTypes) { |
| final int boundIndexOffset; |
| if (((Type) upperBoundTypes.get(0).getUnderlyingType()).isInterface()) { |
| boundIndexOffset = -1; |
| } else { |
| boundIndexOffset = 0; |
| } |
| |
| return boundIndexOffset; |
| } |
| |
| /** |
| * Overload of getTypeAtLocation with default values null/false for the annotation and array |
| * component flag, to make usage easier. Default visibility to allow usage within package. |
| */ |
| static AnnotatedTypeMirror getTypeAtLocation( |
| AnnotatedTypeMirror type, List<TypeAnnotationPosition.TypePathEntry> location) |
| throws UnexpectedAnnotationLocationException { |
| return getTypeAtLocation(type, location, null, false); |
| } |
| |
| /** |
| * Given a TypePath into a type, return the component type that is located at the end of the |
| * TypePath. |
| * |
| * @param type a type containing the type specified by location |
| * @param location a type path into type |
| * @param anno an annotation to be applied to the inner types of a declared type if the declared |
| * type is itself a component type of an array |
| * @param isComponentTypeOfArray indicates whether the type under analysis is a component type of |
| * some array type |
| * @return the type specified by location |
| */ |
| private static AnnotatedTypeMirror getTypeAtLocation( |
| AnnotatedTypeMirror type, |
| List<TypeAnnotationPosition.TypePathEntry> location, |
| TypeCompound anno, |
| boolean isComponentTypeOfArray) |
| throws UnexpectedAnnotationLocationException { |
| if (location.isEmpty() && type.getKind() != TypeKind.DECLARED) { |
| // An annotation with an empty type path on a declared type applies to the outermost enclosing |
| // type. This logic is handled together with non-empty type paths in getLocationTypeADT. For |
| // other kinds of types, no work is required for an empty type path. |
| return type; |
| } |
| switch (type.getKind()) { |
| case NULL: |
| return getLocationTypeANT((AnnotatedNullType) type, location); |
| case DECLARED: |
| return getLocationTypeADT( |
| (AnnotatedDeclaredType) type, location, anno, isComponentTypeOfArray); |
| case WILDCARD: |
| return getLocationTypeAWT((AnnotatedWildcardType) type, location); |
| case TYPEVAR: |
| if (TypesUtils.isCaptured((TypeVariable) type.getUnderlyingType())) { |
| // Work-around for Issue 1696: ignore captured wildcards. |
| // There is no reason to observe such a type and it would be better |
| // to prevent that this type ever reaches this point. |
| return type; |
| } |
| // Raise an error for all other type variables (why isn't this needed?). |
| break; |
| case ARRAY: |
| return getLocationTypeAAT((AnnotatedArrayType) type, location, anno); |
| case UNION: |
| return getLocationTypeAUT((AnnotatedUnionType) type, location); |
| case INTERSECTION: |
| return getLocationTypeAIT((AnnotatedIntersectionType) type, location); |
| default: |
| // Raise an error for all other types below. |
| } |
| throw new UnexpectedAnnotationLocationException( |
| "ElementAnnotationUtil.getTypeAtLocation: " |
| + "unexpected annotation with location found for type: %s (kind: %s ) location: ", |
| type, type.getKind(), location); |
| } |
| |
| /** |
| * Given a TypePath into a declared type, return the component type that is located at the end of |
| * the TypePath. |
| * |
| * @param type a type containing the type specified by location |
| * @param location a type path into type |
| * @param anno an annotation to be applied to the inner types of the declared type if the declared |
| * type is itself a component type of an array |
| * @param isComponentTypeOfArray indicates whether the type under analysis is a component type of |
| * some array type |
| * @return the type specified by location |
| */ |
| @SuppressWarnings("JdkObsolete") // error is issued on every operation, must suppress here |
| private static AnnotatedTypeMirror getLocationTypeADT( |
| AnnotatedDeclaredType type, |
| List<TypeAnnotationPosition.TypePathEntry> location, |
| TypeCompound anno, |
| boolean isComponentTypeOfArray) |
| throws UnexpectedAnnotationLocationException { |
| // List order by outermost type to innermost type. |
| ArrayDeque<AnnotatedDeclaredType> outerToInner = new ArrayDeque<>(); |
| AnnotatedDeclaredType enclosing = type; |
| while (enclosing != null) { |
| outerToInner.addFirst(enclosing); |
| enclosing = enclosing.getEnclosingType(); |
| } |
| |
| // If the AnnotatedDeclaredType is a component of an array type, then apply anno to all |
| // possible inner types. |
| // NOTE: This workaround can be removed once |
| // https://bugs.openjdk.java.net/browse/JDK-8208470 is fixed |
| // The number of enclosing types is outerToInner.size() - 1; there only is |
| // work to do if outerToInner contains more than one element. |
| if (anno != null && isComponentTypeOfArray && location.isEmpty() && outerToInner.size() > 1) { |
| ArrayDeque<AnnotatedDeclaredType> innerTypes = new ArrayDeque<>(outerToInner); |
| innerTypes.removeFirst(); |
| while (!innerTypes.isEmpty()) { |
| innerTypes.removeFirst().addAnnotation(anno); |
| } |
| } |
| |
| // Create a linked list of the location, so removing the first element is easier. |
| // Also, the tail() operation wouldn't work with a Deque. |
| @SuppressWarnings("JdkObsolete") |
| LinkedList<TypePathEntry> tailOfLocations = new LinkedList<>(location); |
| boolean error = false; |
| while (!tailOfLocations.isEmpty()) { |
| TypePathEntry currentLocation = tailOfLocations.removeFirst(); |
| switch (currentLocation.tag) { |
| case INNER_TYPE: |
| outerToInner.removeFirst(); |
| break; |
| case TYPE_ARGUMENT: |
| AnnotatedDeclaredType innerType = outerToInner.getFirst(); |
| if (currentLocation.arg < innerType.getTypeArguments().size()) { |
| AnnotatedTypeMirror typeArg = innerType.getTypeArguments().get(currentLocation.arg); |
| return getTypeAtLocation(typeArg, tailOfLocations); |
| } else { |
| error = true; |
| break; |
| } |
| default: |
| error = true; |
| } |
| if (error) { |
| break; |
| } |
| } |
| |
| if (outerToInner.isEmpty() || error) { |
| throw new UnexpectedAnnotationLocationException( |
| "ElementAnnotationUtil.getLocationTypeADT: invalid location %s for type: %s", |
| location, type); |
| } |
| |
| return outerToInner.getFirst(); |
| } |
| |
| private static AnnotatedTypeMirror getLocationTypeANT( |
| AnnotatedNullType type, List<TypeAnnotationPosition.TypePathEntry> location) |
| throws UnexpectedAnnotationLocationException { |
| if (location.size() == 1 && location.get(0).tag == TypePathEntryKind.TYPE_ARGUMENT) { |
| return type; |
| } |
| |
| throw new UnexpectedAnnotationLocationException( |
| "ElementAnnotationUtil.getLocationTypeANT: " + "invalid location %s for type: %s ", |
| location, type); |
| } |
| |
| private static AnnotatedTypeMirror getLocationTypeAWT( |
| final AnnotatedWildcardType type, final List<TypeAnnotationPosition.TypePathEntry> location) |
| throws UnexpectedAnnotationLocationException { |
| |
| // the last step into the Wildcard type is handled in WildcardToBoundAnnos.addAnnotation |
| if (location.size() == 1) { |
| return type; |
| } |
| |
| if (!location.isEmpty() |
| && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.WILDCARD) { |
| if (AnnotatedTypes.hasExplicitExtendsBound(type)) { |
| return getTypeAtLocation(type.getExtendsBound(), tail(location)); |
| } else if (AnnotatedTypes.hasExplicitSuperBound(type)) { |
| return getTypeAtLocation(type.getSuperBound(), tail(location)); |
| } else { |
| return getTypeAtLocation(type.getExtendsBound(), tail(location)); |
| } |
| |
| } else { |
| throw new UnexpectedAnnotationLocationException( |
| "ElementAnnotationUtil.getLocationTypeAWT: " + "invalid location %s for type: %s ", |
| location, type); |
| } |
| } |
| |
| /** |
| * When we have an (e.g. @Odd int @NonNull []) the type-annotation position of the array |
| * annotation (@NonNull) is really the outermost type in the TypeAnnotationPosition and it will |
| * NOT have TypePathEntryKind.ARRAY at the end of its position. The position of the component type |
| * (@Odd) is considered deeper in the type and therefore has the TypePathEntryKind.ARRAY in its |
| * position. |
| */ |
| private static AnnotatedTypeMirror getLocationTypeAAT( |
| AnnotatedArrayType type, |
| List<TypeAnnotationPosition.TypePathEntry> location, |
| TypeCompound anno) |
| throws UnexpectedAnnotationLocationException { |
| if (location.size() >= 1 |
| && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) { |
| AnnotatedTypeMirror comptype = type.getComponentType(); |
| return getTypeAtLocation(comptype, tail(location), anno, true); |
| } else { |
| throw new UnexpectedAnnotationLocationException( |
| "ElementAnnotationUtil.annotateAAT: " + "invalid location %s for type: %s ", |
| location, type); |
| } |
| } |
| |
| /* |
| * TODO: this case should never occur! |
| * A union type can only occur in special locations, e.g. for exception |
| * parameters. The EXCEPTION_PARAMETER TartetType should be used to |
| * decide which of the alternatives in the union to annotate. |
| * Only the TypePathEntry is not enough. |
| * As a hack, always annotate the first alternative. |
| */ |
| private static AnnotatedTypeMirror getLocationTypeAUT( |
| AnnotatedUnionType type, List<TypeAnnotationPosition.TypePathEntry> location) |
| throws UnexpectedAnnotationLocationException { |
| AnnotatedTypeMirror comptype = type.getAlternatives().get(0); |
| return getTypeAtLocation(comptype, location); |
| } |
| |
| /** Intersection types use the TYPE_ARGUMENT index to separate the individual types. */ |
| private static AnnotatedTypeMirror getLocationTypeAIT( |
| AnnotatedIntersectionType type, List<TypeAnnotationPosition.TypePathEntry> location) |
| throws UnexpectedAnnotationLocationException { |
| if (location.size() >= 1 |
| && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.TYPE_ARGUMENT) { |
| AnnotatedTypeMirror bound = type.getBounds().get(location.get(0).arg); |
| return getTypeAtLocation(bound, tail(location)); |
| } else { |
| throw new UnexpectedAnnotationLocationException( |
| "ElementAnnotationUtil.getLocatonTypeAIT: invalid location %s for type: %s ", |
| location, type); |
| } |
| } |
| |
| private static <T> List<T> tail(List<T> list) { |
| return list.subList(1, list.size()); |
| } |
| |
| /** Exception indicating an invalid location for an annotation was found. */ |
| @SuppressWarnings("serial") |
| public static class UnexpectedAnnotationLocationException extends Exception { |
| |
| /** |
| * Creates an UnexpectedAnnotationLocationException. |
| * |
| * @param format format string |
| * @param args arguments to the format string |
| */ |
| @FormatMethod |
| private UnexpectedAnnotationLocationException(String format, Object... args) { |
| super(String.format(format, args)); |
| } |
| } |
| |
| /** An ERROR TypeKind was found. */ |
| @SuppressWarnings("serial") |
| public static class ErrorTypeKindException extends Error { |
| |
| /** |
| * Creates an ErrorTypeKindException. |
| * |
| * @param format format string |
| * @param args arguments to the format string |
| */ |
| @FormatMethod |
| public ErrorTypeKindException(String format, Object... args) { |
| super(String.format(format, args)); |
| } |
| } |
| } |