package org.checkerframework.framework.util.dependenttypes; import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.LambdaExpressionTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.ModifiersTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; 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.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.framework.qual.JavaExpression; import org.checkerframework.framework.source.Result; import org.checkerframework.framework.source.SourceChecker; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.framework.type.AnnotatedTypeParameterBounds; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.framework.type.visitor.AnnotatedTypeComparer; import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner; import org.checkerframework.framework.util.AnnotationBuilder; import org.checkerframework.framework.util.FlowExpressionParseUtil; import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionContext; import org.checkerframework.framework.util.PluginUtil; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.ErrorReporter; import org.checkerframework.javacutil.InternalUtils; import org.checkerframework.javacutil.TreeUtils; /** * A class that helps checkers use qualifiers that are represented by annotations with Java * expression strings. This class performs four main functions: * * <p>1. Standardizes/canonicalizes the expressions in the annotations such that two expression * strings that are equivalent are made to be equal. For example, an instance field f may appear in * an expression string as "f" or "this.f"; this class standardizes both strings to "this.f". * * <p>2. Viewpoint-adapts annotations on field or method declarations at field accesses or method * invocations. * * <p>3. Changes invalid expression strings to an error string that includes the reason why the * expression is invalid. For example, {@code @KeyFor("m")} would be changed to * {@code @KeyFor("[error for expression: m error: m: identifier not found]")} if m is not a valid * identifier. * * <p>4. Checks annotated types for error strings that have been added by this class and issues an * error if any are found. * * <p>Steps 3 and 4 are separated so that an error is issued only once per invalid expression string * rather than every time the expression string is parsed. (The expression string is parsed multiple * times because annotated types are created multiple times.) */ public class DependentTypesHelper { protected final AnnotatedTypeFactory factory; /** A map of annotation classes to the names of their elements that are Java expressions. */ private Map<Class<? extends Annotation>, List<String>> annoToElements; public DependentTypesHelper(AnnotatedTypeFactory factory) { this.factory = factory; this.annoToElements = new HashMap<>(); for (Class<? extends Annotation> expressionAnno : factory.getSupportedTypeQualifiers()) { List<String> elementList = getExpressionElementNames(expressionAnno); if (elementList != null && !elementList.isEmpty()) { annoToElements.put(expressionAnno, elementList); } } } /** * Returns a list of the names of elements in the annotation class that should be interpreted as * Java expressions. * * @param clazz Annotation class * @return a list of the names of elements in the annotation class that should be interpreted as * Java expressions */ private static List<String> getExpressionElementNames(Class<? extends Annotation> clazz) { Method[] methods = clazz.getMethods(); if (methods == null) { return Collections.emptyList(); } List<String> elements = new ArrayList<>(); for (Method method : methods) { JavaExpression javaExpression = method.getAnnotation(JavaExpression.class); if (javaExpression != null) { elements.add(method.getName()); } } return elements; } /** * Creates a TreeAnnotator that standardizes dependent type annotations. * * @param factory annotated type factory * @return a new TreeAnnotator that standardizes dependent type annotations */ public TreeAnnotator createDependentTypesTreeAnnotator(AnnotatedTypeFactory factory) { return new DependentTypesTreeAnnotator(factory, this); } /** * Viewpoint adapts the dependent type annotations on the bounds to the use of the type. * * @param classDecl class or interface declaration whose type variables should be viewpoint * adapted * @param bounds annotated types of the bounds of the type variables * @param pathToUse tree path to the use of the class or interface */ public void viewpointAdaptTypeVariableBounds( TypeElement classDecl, List<AnnotatedTypeParameterBounds> bounds, TreePath pathToUse) { FlowExpressions.Receiver r = FlowExpressions.internalRepOfImplicitReceiver(classDecl); FlowExpressionContext context = new FlowExpressionContext(r, null, factory.getContext()); for (AnnotatedTypeParameterBounds bound : bounds) { standardizeDoNotUseLocals(context, pathToUse, bound.getUpperBound()); standardizeDoNotUseLocals(context, pathToUse, bound.getLowerBound()); } } /** * Viewpoint adapts the dependent type annotations in the methodDeclType based on the * methodInvocationTree. * * @param methodInvocationTree use of the method * @param methodDeclType type of the method declaration */ public void viewpointAdaptMethod( MethodInvocationTree methodInvocationTree, AnnotatedExecutableType methodDeclType) { ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvocationTree); List<? extends ExpressionTree> args = methodInvocationTree.getArguments(); viewpointAdaptExecutable(methodInvocationTree, receiverTree, methodDeclType, args); } /** * Viewpoint adapts the dependent type annotations in the constructorType based on the * newClassTree. * * @param newClassTree invocation of the constructor * @param constructorType type of the constructor */ public void viewpointAdaptConstructor( NewClassTree newClassTree, AnnotatedExecutableType constructorType) { ExpressionTree receiverTree = newClassTree.getEnclosingExpression(); List<? extends ExpressionTree> args = newClassTree.getArguments(); viewpointAdaptExecutable(newClassTree, receiverTree, constructorType, args); } private void viewpointAdaptExecutable( ExpressionTree tree, ExpressionTree receiverTree, AnnotatedExecutableType typeFromUse, List<? extends ExpressionTree> args) { Element element = TreeUtils.elementFromUse(tree); AnnotatedExecutableType viewpointAdaptedType = (AnnotatedExecutableType) factory.getAnnotatedType(element); if (!hasDependentType(viewpointAdaptedType)) { return; } FlowExpressions.Receiver receiver; if (receiverTree == null) { receiver = FlowExpressions.internalRepOfImplicitReceiver(TreeUtils.elementFromUse(tree)); } else { receiver = FlowExpressions.internalReprOf(factory, receiverTree); } List<FlowExpressions.Receiver> argReceivers = new ArrayList<>(args.size()); for (ExpressionTree argTree : args) { argReceivers.add(FlowExpressions.internalReprOf(factory, argTree)); } TreePath currentPath = factory.getPath(tree); FlowExpressionContext context = new FlowExpressionContext(receiver, argReceivers, factory.getContext()); // typeForUse cannot be viewpoint adapted directly because it is the type post type variable // substitution. Dependent type annotations on type arguments do not (and cannot) be // viewpoint adapted along with the dependent type annotations that are on the method // declaration. For example: // Map<String, String> map = ...; // List<@KeyFor("map") String> list = ...; // list.get(0) // If the type of List.get is viewpoint adapted for the invocation "list.get(0)", then // typeFromUse would be @KeyFor("map") String get(int). // Instead, use the type for the method (viewpointAdaptedType) and viewpoint adapt that // type. // Then copy annotations from the viewpoint adapted type to typeFromUse, if that annotation // is not on a type that was substituted for a type variable. standardizeDoNotUseLocals(context, currentPath, viewpointAdaptedType); new ViewpointAdaptedCopier().visit(viewpointAdaptedType, typeFromUse); } public void standardizeNewClassTree(NewClassTree tree, AnnotatedDeclaredType type) { if (!hasDependentType(type)) { return; } TreePath path = factory.getPath(tree); FlowExpressions.Receiver r = FlowExpressions.internalRepOfImplicitReceiver(TreeUtils.elementFromUse(tree)); FlowExpressionContext context = new FlowExpressionContext( r, FlowExpressions.getParametersOfEnclosingMethod(factory, path), factory.getContext()); standardizeUseLocals(context, path, type); } public void standardizeReturnType(MethodTree m, AnnotatedTypeMirror atm) { if (atm.getKind() == TypeKind.NONE) { return; } if (!hasDependentType(atm)) { return; } Element ele = TreeUtils.elementFromDeclaration(m); TypeMirror enclosingType = ElementUtils.enclosingClass(ele).asType(); FlowExpressionContext context = FlowExpressionContext.buildContextForMethodDeclaration( m, enclosingType, factory.getContext()); standardizeDoNotUseLocals(context, factory.getPath(m), atm); } public void standardizeVariable(Tree node, AnnotatedTypeMirror type, Element ele) { if (!hasDependentType(type)) { return; } TreePath path = factory.getPath(node); if (path == null) { return; } switch (ele.getKind()) { case PARAMETER: Tree enclTree = TreeUtils.enclosingOfKind( path, new HashSet<Kind>( Arrays.asList(Kind.METHOD, Kind.LAMBDA_EXPRESSION))); if (enclTree.getKind() == Kind.METHOD) { // If the most enclosing tree is a method, the parameter is a method parameter MethodTree methodTree = (MethodTree) enclTree; TypeMirror enclosingType = ElementUtils.enclosingClass(ele).asType(); FlowExpressionContext parameterContext = FlowExpressionContext.buildContextForMethodDeclaration( methodTree, enclosingType, factory.getContext()); standardizeDoNotUseLocals(parameterContext, path, type); } else { // Otherwise, the parameter is a lambda parameter LambdaExpressionTree lambdaTree = (LambdaExpressionTree) enclTree; FlowExpressionContext parameterContext = FlowExpressionContext.buildContextForLambda( lambdaTree, path, factory.getContext()); // TODO: test this. // TODO: use path.getParentPath to prevent a StackOverflowError, see Issue #1027. standardizeUseLocals(parameterContext, path.getParentPath(), type); } break; case LOCAL_VARIABLE: case RESOURCE_VARIABLE: case EXCEPTION_PARAMETER: TypeMirror enclosingType = ElementUtils.enclosingClass(ele).asType(); FlowExpressions.Receiver receiver = FlowExpressions.internalRepOfPseudoReceiver(path, enclosingType); List<Receiver> params = FlowExpressions.getParametersOfEnclosingMethod(factory, path); FlowExpressionContext localContext = new FlowExpressionContext(receiver, params, factory.getContext()); standardizeUseLocals(localContext, path, type); break; case FIELD: FlowExpressions.Receiver receiverF; if (node.getKind() == Tree.Kind.IDENTIFIER) { FlowExpressions.Receiver r = FlowExpressions.internalReprOf(factory, (IdentifierTree) node); receiverF = r instanceof FlowExpressions.FieldAccess ? ((FlowExpressions.FieldAccess) r).getReceiver() : r; } else { receiverF = FlowExpressions.internalRepOfImplicitReceiver(ele); } FlowExpressionContext fieldContext = new FlowExpressionContext(receiverF, null, factory.getContext()); standardizeDoNotUseLocals(fieldContext, path, type); break; default: // Nothing to do. } } public void standardizeFieldAccess(MemberSelectTree node, AnnotatedTypeMirror type) { if (!hasDependentType(type)) { return; } if (TreeUtils.isClassLiteral(node)) { return; } Element ele = TreeUtils.elementFromUse(node); if (ele.getKind() != ElementKind.FIELD) { return; } FlowExpressions.Receiver receiver = FlowExpressions.internalReprOf(factory, node.getExpression()); FlowExpressionContext context = new FlowExpressionContext(receiver, null, factory.getContext()); standardizeDoNotUseLocals(context, factory.getPath(node), type); } public void standardizeExpression(ExpressionTree tree, AnnotatedTypeMirror annotatedType) { if (!hasDependentType(annotatedType)) { return; } TreePath path = factory.getPath(tree); if (path == null) { return; } Tree enclosingClass = TreeUtils.enclosingClass(path); if (enclosingClass == null) { return; } TypeMirror enclosingType = InternalUtils.typeOf(enclosingClass); FlowExpressions.Receiver receiver = FlowExpressions.internalRepOfPseudoReceiver(path, enclosingType); FlowExpressionContext localContext = new FlowExpressionContext( receiver, FlowExpressions.getParametersOfEnclosingMethod(factory, path), factory.getContext()); standardizeUseLocals(localContext, path, annotatedType); } public void standardizeVariable(AnnotatedTypeMirror type, Element elt) { if (!hasDependentType(type)) { return; } switch (elt.getKind()) { case PARAMETER: case LOCAL_VARIABLE: case RESOURCE_VARIABLE: case EXCEPTION_PARAMETER: Tree tree = factory.declarationFromElement(elt); if (tree == null) { if (elt.getKind() == ElementKind.PARAMETER) { // The tree might be null when // org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() // gets the assignment context for a pseudo assignment of an argument to // a method parameter. return; } ErrorReporter.errorAbort(this.getClass() + ": tree not found"); } else if (InternalUtils.typeOf(tree) == null) { // org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() // gets the assignment context for a pseudo assignment of an argument to // a method parameter. return; } standardizeVariable(tree, type, elt); break; default: // Nothing to do. } } private void standardizeUseLocals( FlowExpressionContext context, TreePath localScope, AnnotatedTypeMirror type) { standardizeAtm(context, localScope, type, true); } private void standardizeDoNotUseLocals( FlowExpressionContext context, TreePath localScope, AnnotatedTypeMirror type) { standardizeAtm(context, localScope, type, false); } private void standardizeAtm( FlowExpressionContext context, TreePath localScope, AnnotatedTypeMirror type, boolean useLocalScope) { // localScope is null in dataflow when creating synthetic trees for enhanced for loops. if (localScope != null) { new StandardizeTypeAnnotator(context, localScope, useLocalScope).visit(type); } } protected String standardizeString( String expression, FlowExpressionContext context, TreePath localScope, boolean useLocalScope) { if (DependentTypesError.isExpressionError(expression)) { return expression; } try { FlowExpressions.Receiver result = FlowExpressionParseUtil.parse(expression, context, localScope, useLocalScope); if (result == null) { return new DependentTypesError(expression, " ").toString(); } return result.toString(); } catch (FlowExpressionParseUtil.FlowExpressionParseException e) { return new DependentTypesError(expression, e).toString(); } } /** * Returns true if any qualifier in the type system is a dependent type annotation. * * @return true if any qualifier in the type system is a dependent type annotation */ public boolean hasDependentAnnotations() { return !annoToElements.isEmpty(); } private class StandardizeTypeAnnotator extends AnnotatedTypeScanner<Void, Void> { private final FlowExpressionContext context; private final TreePath localScope; /** Whether or not the expression might contain a variable declared in local scope */ private final boolean useLocalScope; private StandardizeTypeAnnotator( FlowExpressionContext context, TreePath localScope, boolean useLocalScope) { this.context = context; this.localScope = localScope; this.useLocalScope = useLocalScope; } private AnnotationMirror standardizeAnnotation( FlowExpressionContext context, TreePath localScope, AnnotationMirror anno, boolean useLocalScope) { if (!isExpressionAnno(anno)) { return null; } AnnotationBuilder builder = new AnnotationBuilder( factory.getProcessingEnv(), AnnotationUtils.annotationName(anno)); for (String value : getListOfExpressionElements(anno)) { List<String> expressionStrings = AnnotationUtils.getElementValueArray(anno, value, String.class, true); List<String> standardizedStrings = new ArrayList<>(); for (String expression : expressionStrings) { standardizedStrings.add( standardizeString(expression, context, localScope, useLocalScope)); } builder.setValue(value, standardizedStrings); } return builder.build(); } @Override public Void visitTypeVariable(AnnotatedTypeMirror.AnnotatedTypeVariable type, Void aVoid) { if (visitedNodes.containsKey(type)) { return visitedNodes.get(type); } visitedNodes.put(type, null); // If the type variable has a primary annotation, then it is viewpoint adapted then // copied to the upper and lower bounds. Attempting to viewpoint adapt again, could // cause the flow expression parser to fail. So, remove the primary annotations from // the upper and lower bound before they are recursively visited. Then add them back. Set<AnnotationMirror> primarys = type.getAnnotations(); type.getLowerBound().removeAnnotations(primarys); Void r = scan(type.getLowerBound(), aVoid); type.getLowerBound().addAnnotations(primarys); visitedNodes.put(type, r); type.getUpperBound().removeAnnotations(primarys); r = scanAndReduce(type.getUpperBound(), aVoid, r); type.getUpperBound().addAnnotations(primarys); visitedNodes.put(type, r); return r; } @Override protected Void scan(AnnotatedTypeMirror type, Void aVoid) { if (type == null) { return null; } List<AnnotationMirror> newAnnos = new ArrayList<>(); for (AnnotationMirror anno : type.getAnnotations()) { AnnotationMirror annotationMirror = standardizeAnnotation(context, localScope, anno, useLocalScope); if (annotationMirror != null) { newAnnos.add(annotationMirror); } } for (AnnotationMirror anno : newAnnos) { // More than one annotation of the same class might have been written into // the element and therefore might appear more than once in the type. // See PR #674 // https://github.com/typetools/checker-framework/pull/674 // Work around this bug by remove all annotations of the same class. if (type.removeAnnotation(anno)) { type.removeAnnotation(anno); } } type.addAnnotations(newAnnos); return super.scan(type, aVoid); } } /** * Checks all expression in the given annotated type to see if the expression string is an error * string as specified by {@link DependentTypesError#isExpressionError}. If the annotated type * has any errors, a flowexpr.parse.error is issued at the errorTree. * * @param atm annotated type to check for expression errors * @param errorTree the tree at which to report any found errors */ public void checkType(AnnotatedTypeMirror atm, Tree errorTree) { List<DependentTypesError> errors = new ExpressionErrorChecker().visit(atm); if (errors == null || errors.isEmpty()) { return; } if (errorTree.getKind() == Kind.VARIABLE) { ModifiersTree modifiers = ((VariableTree) errorTree).getModifiers(); errorTree = ((VariableTree) errorTree).getType(); for (AnnotationTree annoTree : modifiers.getAnnotations()) { for (Class<?> annoClazz : annoToElements.keySet()) { if (annoTree.toString().contains(annoClazz.getSimpleName())) { errorTree = annoTree; break; } } } } reportErrors(errorTree, errors); } protected void reportErrors(Tree errorTree, List<DependentTypesError> errors) { if (errors.isEmpty()) { return; } SourceChecker checker = factory.getContext().getChecker(); String error = PluginUtil.join("\n", errors); checker.report(Result.failure("expression.unparsable.type.invalid", error), errorTree); } /** * Checks all expressions in the method declaration AnnotatedTypeMirror to see if the expression * string is an error string as specified by DependentTypesError#isExpressionError. If the * annotated type has any errors, a flowexpr.parse.error is issued. * * @param methodTree method to check * @param type annotated type of the method */ public void checkMethod(MethodTree methodTree, AnnotatedExecutableType type) { // Parameters and receivers are checked by visitVariable // So only type parameters and return type need to be checked here. checkTypeVariables(methodTree, type); // Check return type if (type.getReturnType().getKind() != TypeKind.VOID) { AnnotatedTypeMirror returnType = factory.getMethodReturnType(methodTree); checkType(returnType, methodTree.getReturnType()); } } private void checkTypeVariables(MethodTree node, AnnotatedExecutableType methodType) { Element ele = TreeUtils.elementFromDeclaration(node); TypeMirror enclosingType = ElementUtils.enclosingClass(ele).asType(); FlowExpressionContext context = FlowExpressionContext.buildContextForMethodDeclaration( node, enclosingType, factory.getContext()); for (int i = 0; i < methodType.getTypeVariables().size(); i++) { AnnotatedTypeMirror atm = methodType.getTypeVariables().get(i); standardizeDoNotUseLocals(context, factory.getPath(node), atm); checkType(atm, node.getTypeParameters().get(i)); } } private boolean isExpressionAnno(AnnotationMirror am) { for (Class<? extends Annotation> clazz : annoToElements.keySet()) { if (AnnotationUtils.areSameByClass(am, clazz)) { return true; } } return false; } /** * Checks all dependent type annotations in the given annotated type to see if the expression * string is an error string as specified by DependentTypesError#isExpressionError. If the * annotated type has any errors, then a non-empty list of {@link DependentTypesError} is * returned. */ private class ExpressionErrorChecker extends AnnotatedTypeScanner<List<DependentTypesError>, Void> { @Override protected List<DependentTypesError> scan(AnnotatedTypeMirror type, Void aVoid) { if (type == null) { return super.scan(type, aVoid); } List<DependentTypesError> errors = new ArrayList<>(); for (AnnotationMirror am : type.getAnnotations()) { if (isExpressionAnno(am)) { errors.addAll(checkForError(am)); } } List<DependentTypesError> superList = super.scan(type, aVoid); if (superList != null) { errors.addAll(superList); } return errors; } @Override protected List<DependentTypesError> reduce( List<DependentTypesError> r1, List<DependentTypesError> r2) { if (r1 != null && r2 != null) { r1.addAll(r2); return r1; } else if (r1 != null) { return r1; } else if (r2 != null) { return r2; } else { return null; } } /** * Checks every Java expression element of the annotation to see if the expression is an * error string as specified by DependentTypesError#isExpressionError. If any expression is * an error, then a non-empty list of {@link DependentTypesError} is returned. */ private List<DependentTypesError> checkForError(AnnotationMirror am) { List<DependentTypesError> errors = new ArrayList<>(); for (String element : getListOfExpressionElements(am)) { List<String> value = AnnotationUtils.getElementValueArray(am, element, String.class, true); for (String v : value) { if (DependentTypesError.isExpressionError(v)) { errors.add(new DependentTypesError(v)); } } } return errors; } } /** Copies annotations that might have been viewpoint adapted from type to the parameter. */ private class ViewpointAdaptedCopier extends AnnotatedTypeComparer<Void> { @Override protected Void scan(AnnotatedTypeMirror type, AnnotatedTypeMirror p) { if (type == null || p == null) { return null; } Set<AnnotationMirror> replacement = AnnotationUtils.createAnnotationSet(); for (Class<? extends Annotation> vpa : annoToElements.keySet()) { AnnotationMirror anno = type.getAnnotation(vpa); if (anno != null) { // Only replace annotations that might have been changed. replacement.add(anno); } } p.replaceAnnotations(replacement); if (type.getKind() != p.getKind()) { // if the underlying types don't match, then this type has be substituted for a // type variable, so don't recur. The primary annotation was copied because // if the type variable might have had a primary annotation at a use. // For example: // <T> void method(@KeyFor("a") T t) {...} // void use(@KeyFor("b") String s) { // method(s); // the type of the parameter should be @KeyFor("a") String // } return null; } return super.scan(type, p); } @Override protected Void compare(AnnotatedTypeMirror type, AnnotatedTypeMirror p) { if (type == null || p == null) { return null; } if (type.getKind() != p.getKind()) { ErrorReporter.errorAbort("Should be the same. type: %s p: %s ", type, p); } return null; } @Override protected Void combineRs(Void r1, Void r2) { return null; } } /** * Whether or not atm has any dependent type annotations. If an annotated type does not have a * dependent type annotation, then no standardization or viewpoint adaption is performed. (This * check avoids calling time intensive methods unless absolutely required.) */ private boolean hasDependentType(AnnotatedTypeMirror atm) { if (atm == null) { return false; } Boolean b = new ContainsDependentType().visit(atm); if (b == null) { return false; } return b; } /** Checks whether or not an annotated type contains an dependent type annotation. */ private class ContainsDependentType extends AnnotatedTypeScanner<Boolean, Void> { @Override protected Boolean scan(AnnotatedTypeMirror type, Void aVoid) { if (type == null) { return false; } for (AnnotationMirror am : type.getAnnotations()) { if (isExpressionAnno(am)) { return true; } } return super.scan(type, aVoid); } @Override protected Boolean reduce(Boolean r1, Boolean r2) { if (r1 != null && r2 != null) { // if either have an expression anno, return true; return r1 || r2; } else if (r1 != null) { return r1; } else if (r2 != null) { return r2; } else { return false; } } } /** * Returns the list of elements of the annotation that are Java expressions, or the empty list * if there aren't any. * * @param am AnnotationMirror * @return Returns the list of elements of the annotation that are Java expressions, or the * empty list if there aren't any. */ private List<String> getListOfExpressionElements(AnnotationMirror am) { for (Class<? extends Annotation> clazz : annoToElements.keySet()) { if (AnnotationUtils.areSameByClass(am, clazz)) { return annoToElements.get(clazz); } } return Collections.emptyList(); } }