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 javax.lang.model.element.Element; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.util.PluginUtil; import org.checkerframework.javacutil.ErrorReporter; /** * 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; /** * @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(); /** * @return 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. */ 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<Attribute.TypeCompound> targeted); /** * 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) {} /** * @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) { if (!invalid.isEmpty()) { ErrorReporter.errorAbort( this.getClass().getName() + ".handleInvalid: " + "Invalid variable and element passed to extractAndApply; type: " + type + "," + " element: " + element + " (kind: " + element.getKind() + "), invalid annotations: " + PluginUtil.join(", ", invalid) + "\n" + "Targeted annotations: " + PluginUtil.join(", ", annotatedTargets()) + "; Valid annotations: " + PluginUtil.join(", ", validTargets())); } } /** * 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<TypeCompound>()); } 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() { if (!isAccepted()) { ErrorReporter.errorAbort( "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)); } }