| 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 java.util.ArrayList; |
| import java.util.EnumMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.StringJoiner; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.type.TypeKind; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; |
| import org.checkerframework.javacutil.BugInCF; |
| import org.plumelib.util.StringsPlume; |
| |
| /** |
| * TargetedElementAnnotationApplier filters annotations for an element into 3 groups. TARGETED |
| * annotations are those we wish to apply in this ElementAnnotationApplier. VALID annotations are |
| * those that are valid on the current element (or its enclosure, see getRawTypeAttributes) but |
| * should not be applied to the given type. Invalid annotations are those that should NEVER appear |
| * for the given element. Invalid annotations are reported as errors by default in the handleInvalid |
| * method. See method extractAndApply. Please read getRawTypeAttributes for an idea of what types of |
| * annotations may be encountered by this ElementAnnotationApplier. |
| * |
| * <p>Note: Subtypes of this class likely want to implement the handleTargeted and handleValid |
| * methods though they have default empty implementations for brevity. |
| */ |
| abstract class TargetedElementAnnotationApplier { |
| /** |
| * Three annotation types that may be encountered when calling getRawTypeAttributes. see sift(). |
| */ |
| static enum TargetClass { |
| TARGETED, |
| VALID, |
| INVALID |
| } |
| |
| /** The type to which we wish to apply annotations. */ |
| protected final AnnotatedTypeMirror type; |
| |
| /** An Element that type represents. */ |
| protected final Element element; |
| |
| /** |
| * Returns the TargetTypes that identify annotations we wish to apply with this object. Any |
| * annotations that have these target types will be passed to handleTargeted. |
| * |
| * @return the TargetTypes that identify annotations we wish to apply with this object. Any |
| * annotations that have these target types will be passed to handleTargeted |
| */ |
| protected abstract TargetType[] annotatedTargets(); |
| |
| /** |
| * Returns the TargetTypes that identify annotations that are valid but we wish to ignore. Any |
| * annotations that have these target types will be passed to handleValid, providing they aren't |
| * also in annotatedTargets. |
| * |
| * @return the TargetTypes that identify annotations that are valid but we wish to ignore |
| */ |
| protected abstract TargetType[] validTargets(); |
| |
| /** |
| * Annotations on elements are represented as Attribute.TypeCompounds ( a subtype of |
| * AnnotationMirror) that are usually accessed through a getRawTypeAttributes method on the |
| * element. |
| * |
| * <p>In Java 8 and later these annotations are generally contained by elements to which they |
| * apply. However, in earlier versions of Java many of these annotations are handled by either the |
| * enclosing method, e.g. parameters and method type parameters, or enclosing class, e.g. class |
| * type parameters. Therefore, many annotations are addressed by first getting all annotations on |
| * a method or class and the picking out only the ones we wish to target (see extractAndApply). |
| * |
| * @return the annotations that we MAY wish to apply to the given type |
| */ |
| protected abstract Iterable<Attribute.TypeCompound> getRawTypeAttributes(); |
| |
| /** |
| * Tests element/type fields to ensure that this TargetedElementAnnotationApplier is valid for |
| * this element/type pair. |
| * |
| * @return true if the type/element members are handled by this class false otherwise |
| */ |
| protected abstract boolean isAccepted(); |
| |
| /** |
| * @param type the type to annotate |
| * @param element an element identifying type |
| */ |
| TargetedElementAnnotationApplier(final AnnotatedTypeMirror type, final Element element) { |
| this.type = type; |
| this.element = element; |
| } |
| |
| /** |
| * This method should apply all annotations that are handled by this object. |
| * |
| * @param targeted the list of annotations that were returned by getRawTypeAttributes and had a |
| * TargetType contained by annotatedTargets |
| */ |
| protected abstract void handleTargeted(List<TypeCompound> targeted) |
| throws UnexpectedAnnotationLocationException; |
| |
| /** |
| * The default implementation of this method does nothing. |
| * |
| * @param valid the list of annotations that were returned by getRawTypeAttributes and had a |
| * TargetType contained by valid and NOT annotatedTargets |
| */ |
| protected void handleValid(List<Attribute.TypeCompound> valid) {} |
| |
| /** |
| * This implementation reports all invalid annotations as errors. |
| * |
| * @param invalid the list of annotations that were returned by getRawTypeAttributes and were not |
| * handled by handleTargeted or handleValid |
| */ |
| protected void handleInvalid(List<Attribute.TypeCompound> invalid) { |
| List<Attribute.TypeCompound> remaining = new ArrayList<>(invalid.size()); |
| for (Attribute.TypeCompound tc : invalid) { |
| if (tc.getAnnotationType().getKind() != TypeKind.ERROR) { |
| // Filter out annotations that have an error type. javac will |
| // already have raised an error for them. |
| remaining.add(tc); |
| } |
| } |
| if (!remaining.isEmpty()) { |
| StringJoiner msg = new StringJoiner(System.lineSeparator()); |
| msg.add("handleInvalid(this=" + this.getClass().getName() + "):"); |
| msg.add("Invalid variable and element passed to extractAndApply; type: " + type); |
| String elementInfoPrefix = |
| " element: " + element + " (kind: " + element.getKind() + "), invalid annotations: "; |
| StringJoiner remainingInfo = new StringJoiner(", ", elementInfoPrefix, ""); |
| for (Attribute.TypeCompound r : remaining) { |
| remainingInfo.add(r.toString() + " (" + r.position + ")"); |
| } |
| msg.add(remainingInfo.toString()); |
| msg.add("Targeted annotations: " + StringsPlume.join(", ", annotatedTargets())); |
| msg.add("Valid annotations: " + StringsPlume.join(", ", validTargets())); |
| |
| throw new BugInCF(msg.toString()); |
| } |
| } |
| |
| /** |
| * Separate the input annotations into a Map of TargetClass (TARGETED, VALID, INVALID) to the |
| * annotations that fall into each of those categories. |
| * |
| * @param typeCompounds annotations to sift through, should be those returned by |
| * getRawTypeAttributes |
| * @return a {@literal Map<TargetClass => Annotations>.} |
| */ |
| protected Map<TargetClass, List<Attribute.TypeCompound>> sift( |
| final Iterable<Attribute.TypeCompound> typeCompounds) { |
| |
| final Map<TargetClass, List<Attribute.TypeCompound>> targetClassToCompound = |
| new EnumMap<>(TargetClass.class); |
| for (TargetClass targetClass : TargetClass.values()) { |
| targetClassToCompound.put(targetClass, new ArrayList<>()); |
| } |
| |
| for (final Attribute.TypeCompound typeCompound : typeCompounds) { |
| final TargetType typeCompoundTarget = typeCompound.position.type; |
| final List<Attribute.TypeCompound> destList; |
| |
| if (ElementAnnotationUtil.contains(typeCompoundTarget, annotatedTargets())) { |
| destList = targetClassToCompound.get(TargetClass.TARGETED); |
| |
| } else if (ElementAnnotationUtil.contains(typeCompoundTarget, validTargets())) { |
| destList = targetClassToCompound.get(TargetClass.VALID); |
| |
| } else { |
| destList = targetClassToCompound.get(TargetClass.INVALID); |
| } |
| |
| destList.add(typeCompound); |
| } |
| |
| return targetClassToCompound; |
| } |
| |
| /** |
| * Reads the list of annotations that apply to this element (see getRawTypeAttributes). Sifts them |
| * into three groups (TARGETED, INVALID, VALID) and then calls the appropriate handle method on |
| * them. The handleTargeted method should apply all annotations that are handled by this object. |
| * |
| * <p>This method will throw a runtime exception if isAccepted returns false. |
| */ |
| public void extractAndApply() throws UnexpectedAnnotationLocationException { |
| if (!isAccepted()) { |
| throw new BugInCF( |
| "LocalVariableExtractor.extractAndApply: " |
| + "Invalid variable and element passed to " |
| + this.getClass().getName() |
| + "::extractAndApply (" |
| + type |
| + ", " |
| + element); |
| } |
| |
| final Map<TargetClass, List<Attribute.TypeCompound>> targetClassToAnno = |
| sift(getRawTypeAttributes()); |
| |
| handleInvalid(targetClassToAnno.get(TargetClass.INVALID)); |
| handleValid(targetClassToAnno.get(TargetClass.VALID)); |
| handleTargeted(targetClassToAnno.get(TargetClass.TARGETED)); |
| } |
| } |