1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.utils;
21  
22  import java.util.List;
23  import java.util.function.Predicate;
24  
25  import com.puppycrawl.tools.checkstyle.api.DetailAST;
26  import com.puppycrawl.tools.checkstyle.api.FullIdent;
27  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28  
29  /**
30   * Contains utility methods designed to work with annotations.
31   *
32   */
33  public final class AnnotationUtil {
34  
35      /**
36       * Common message.
37       */
38      private static final String THE_AST_IS_NULL = "the ast is null";
39  
40      /**
41       * Private utility constructor.
42       * @throws UnsupportedOperationException if called
43       */
44      private AnnotationUtil() {
45          throw new UnsupportedOperationException("do not instantiate.");
46      }
47  
48      /**
49       * Checks if the AST is annotated with the passed in annotation.
50       *
51       * <p>
52       * This method will not look for imports or package
53       * statements to detect the passed in annotation.
54       * </p>
55       *
56       * <p>
57       * To check if an AST contains a passed in annotation
58       * taking into account fully-qualified names
59       * (ex: java.lang.Override, Override)
60       * this method will need to be called twice. Once for each
61       * name given.
62       * </p>
63       *
64       * @param ast the current node
65       * @param annotation the annotation name to check for
66       * @return true if contains the annotation
67       */
68      public static boolean containsAnnotation(final DetailAST ast,
69          String annotation) {
70          return getAnnotation(ast, annotation) != null;
71      }
72  
73      /**
74       * Checks if the AST is annotated with any annotation.
75       *
76       * @param ast the current node
77       * @return {@code true} if the AST contains at least one annotation
78       */
79      public static boolean containsAnnotation(final DetailAST ast) {
80          if (ast == null) {
81              throw new IllegalArgumentException(THE_AST_IS_NULL);
82          }
83          final DetailAST holder = getAnnotationHolder(ast);
84          return holder != null && holder.findFirstToken(TokenTypes.ANNOTATION) != null;
85      }
86  
87      /**
88       * Checks if the given AST element is annotated with any of the specified annotations.
89       *
90       * <p>
91       * This method accepts both simple and fully-qualified names,
92       * e.g. "Override" will match both java.lang.Override and Override.
93       * </p>
94       *
95       * @param ast The type or method definition.
96       * @param annotations A collection of annotations to look for.
97       * @return {@code true} if the given AST element is annotated with
98       *                      at least one of the specified annotations;
99       *                      {@code false} otherwise.
100      */
101     public static boolean containsAnnotation(DetailAST ast, List<String> annotations) {
102         if (ast == null) {
103             throw new IllegalArgumentException(THE_AST_IS_NULL);
104         }
105 
106         if (annotations == null) {
107             throw new IllegalArgumentException("annotations cannot be null");
108         }
109 
110         boolean result = false;
111 
112         if (!annotations.isEmpty()) {
113             final DetailAST firstMatchingAnnotation = findFirstAnnotation(ast, annotationNode -> {
114                 DetailAST identNode = annotationNode.findFirstToken(TokenTypes.IDENT);
115                 if (identNode == null) {
116                     identNode = annotationNode.findFirstToken(TokenTypes.DOT)
117                             .findFirstToken(TokenTypes.IDENT);
118                 }
119 
120                 return annotations.contains(identNode.getText());
121             });
122             result = firstMatchingAnnotation != null;
123         }
124 
125         return result;
126     }
127 
128     /**
129      * Gets the AST that holds a series of annotations for the
130      * potentially annotated AST.  Returns {@code null}
131      * if the passed in AST does not have an Annotation Holder.
132      *
133      * @param ast the current node
134      * @return the Annotation Holder
135      */
136     public static DetailAST getAnnotationHolder(DetailAST ast) {
137         if (ast == null) {
138             throw new IllegalArgumentException(THE_AST_IS_NULL);
139         }
140 
141         final DetailAST annotationHolder;
142 
143         if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF
144             || ast.getType() == TokenTypes.PACKAGE_DEF) {
145             annotationHolder = ast.findFirstToken(TokenTypes.ANNOTATIONS);
146         }
147         else {
148             annotationHolder = ast.findFirstToken(TokenTypes.MODIFIERS);
149         }
150 
151         return annotationHolder;
152     }
153 
154     /**
155      * Checks if the AST is annotated with the passed in annotation
156      * and returns the AST representing that annotation.
157      *
158      * <p>
159      * This method will not look for imports or package
160      * statements to detect the passed in annotation.
161      * </p>
162      *
163      * <p>
164      * To check if an AST contains a passed in annotation
165      * taking into account fully-qualified names
166      * (ex: java.lang.Override, Override)
167      * this method will need to be called twice. Once for each
168      * name given.
169      * </p>
170      *
171      * @param ast the current node
172      * @param annotation the annotation name to check for
173      * @return the AST representing that annotation
174      */
175     public static DetailAST getAnnotation(final DetailAST ast,
176         String annotation) {
177         if (ast == null) {
178             throw new IllegalArgumentException(THE_AST_IS_NULL);
179         }
180 
181         if (annotation == null) {
182             throw new IllegalArgumentException("the annotation is null");
183         }
184 
185         if (CommonUtil.isBlank(annotation)) {
186             throw new IllegalArgumentException(
187                     "the annotation is empty or spaces");
188         }
189 
190         return findFirstAnnotation(ast, annotationNode -> {
191             final DetailAST firstChild = annotationNode.findFirstToken(TokenTypes.AT);
192             final String name =
193                     FullIdent.createFullIdent(firstChild.getNextSibling()).getText();
194             return annotation.equals(name);
195         });
196     }
197 
198     /**
199      * Checks if the given AST is annotated with at least one annotation that
200      * matches the given predicate and returns the AST representing the first
201      * matching annotation.
202      *
203      * <p>
204      * This method will not look for imports or package
205      * statements to detect the passed in annotation.
206      * </p>
207      *
208      * @param ast the current node
209      * @param predicate The predicate which decides if an annotation matches
210      * @return the AST representing that annotation
211      */
212     private static DetailAST findFirstAnnotation(final DetailAST ast,
213                                                  Predicate<DetailAST> predicate) {
214         final DetailAST holder = getAnnotationHolder(ast);
215         DetailAST result = null;
216         for (DetailAST child = holder.getFirstChild();
217             child != null; child = child.getNextSibling()) {
218             if (child.getType() == TokenTypes.ANNOTATION && predicate.test(child)) {
219                 result = child;
220                 break;
221             }
222         }
223 
224         return result;
225     }
226 
227 }