001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.utils;
021
022import java.util.List;
023import java.util.function.Predicate;
024
025import com.puppycrawl.tools.checkstyle.api.DetailAST;
026import com.puppycrawl.tools.checkstyle.api.FullIdent;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028
029/**
030 * Contains utility methods designed to work with annotations.
031 *
032 */
033public final class AnnotationUtil {
034
035    /**
036     * Common message.
037     */
038    private static final String THE_AST_IS_NULL = "the ast is null";
039
040    /**
041     * Private utility constructor.
042     * @throws UnsupportedOperationException if called
043     */
044    private AnnotationUtil() {
045        throw new UnsupportedOperationException("do not instantiate.");
046    }
047
048    /**
049     * Checks if the AST is annotated with the passed in annotation.
050     *
051     * <p>
052     * This method will not look for imports or package
053     * statements to detect the passed in annotation.
054     * </p>
055     *
056     * <p>
057     * To check if an AST contains a passed in annotation
058     * taking into account fully-qualified names
059     * (ex: java.lang.Override, Override)
060     * this method will need to be called twice. Once for each
061     * name given.
062     * </p>
063     *
064     * @param ast the current node
065     * @param annotation the annotation name to check for
066     * @return true if contains the annotation
067     */
068    public static boolean containsAnnotation(final DetailAST ast,
069        String annotation) {
070        return getAnnotation(ast, annotation) != null;
071    }
072
073    /**
074     * Checks if the AST is annotated with any annotation.
075     *
076     * @param ast the current node
077     * @return {@code true} if the AST contains at least one annotation
078     * @throws IllegalArgumentException when ast is null
079     */
080    public static boolean containsAnnotation(final DetailAST ast) {
081        if (ast == null) {
082            throw new IllegalArgumentException(THE_AST_IS_NULL);
083        }
084        final DetailAST holder = getAnnotationHolder(ast);
085        return holder != null && holder.findFirstToken(TokenTypes.ANNOTATION) != null;
086    }
087
088    /**
089     * Checks if the given AST element is annotated with any of the specified annotations.
090     *
091     * <p>
092     * This method accepts both simple and fully-qualified names,
093     * e.g. "Override" will match both java.lang.Override and Override.
094     * </p>
095     *
096     * @param ast The type or method definition.
097     * @param annotations A collection of annotations to look for.
098     * @return {@code true} if the given AST element is annotated with
099     *                      at least one of the specified annotations;
100     *                      {@code false} otherwise.
101     * @throws IllegalArgumentException when ast or annotations are null
102     */
103    public static boolean containsAnnotation(DetailAST ast, List<String> annotations) {
104        if (ast == null) {
105            throw new IllegalArgumentException(THE_AST_IS_NULL);
106        }
107
108        if (annotations == null) {
109            throw new IllegalArgumentException("annotations cannot be null");
110        }
111
112        boolean result = false;
113
114        if (!annotations.isEmpty()) {
115            final DetailAST firstMatchingAnnotation = findFirstAnnotation(ast, annotationNode -> {
116                DetailAST identNode = annotationNode.findFirstToken(TokenTypes.IDENT);
117                if (identNode == null) {
118                    identNode = annotationNode.findFirstToken(TokenTypes.DOT)
119                            .findFirstToken(TokenTypes.IDENT);
120                }
121
122                return annotations.contains(identNode.getText());
123            });
124            result = firstMatchingAnnotation != null;
125        }
126
127        return result;
128    }
129
130    /**
131     * Gets the AST that holds a series of annotations for the
132     * potentially annotated AST.  Returns {@code null}
133     * if the passed in AST does not have an Annotation Holder.
134     *
135     * @param ast the current node
136     * @return the Annotation Holder
137     * @throws IllegalArgumentException when ast is null
138     */
139    public static DetailAST getAnnotationHolder(DetailAST ast) {
140        if (ast == null) {
141            throw new IllegalArgumentException(THE_AST_IS_NULL);
142        }
143
144        final DetailAST annotationHolder;
145
146        if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF
147            || ast.getType() == TokenTypes.PACKAGE_DEF) {
148            annotationHolder = ast.findFirstToken(TokenTypes.ANNOTATIONS);
149        }
150        else {
151            annotationHolder = ast.findFirstToken(TokenTypes.MODIFIERS);
152        }
153
154        return annotationHolder;
155    }
156
157    /**
158     * Checks if the AST is annotated with the passed in annotation
159     * and returns the AST representing that annotation.
160     *
161     * <p>
162     * This method will not look for imports or package
163     * statements to detect the passed in annotation.
164     * </p>
165     *
166     * <p>
167     * To check if an AST contains a passed in annotation
168     * taking into account fully-qualified names
169     * (ex: java.lang.Override, Override)
170     * this method will need to be called twice. Once for each
171     * name given.
172     * </p>
173     *
174     * @param ast the current node
175     * @param annotation the annotation name to check for
176     * @return the AST representing that annotation
177     * @throws IllegalArgumentException when ast or annotations are null; when annotation is blank
178     */
179    public static DetailAST getAnnotation(final DetailAST ast,
180        String annotation) {
181        if (ast == null) {
182            throw new IllegalArgumentException(THE_AST_IS_NULL);
183        }
184
185        if (annotation == null) {
186            throw new IllegalArgumentException("the annotation is null");
187        }
188
189        if (CommonUtil.isBlank(annotation)) {
190            throw new IllegalArgumentException(
191                    "the annotation is empty or spaces");
192        }
193
194        return findFirstAnnotation(ast, annotationNode -> {
195            final DetailAST firstChild = annotationNode.findFirstToken(TokenTypes.AT);
196            final String name =
197                    FullIdent.createFullIdent(firstChild.getNextSibling()).getText();
198            return annotation.equals(name);
199        });
200    }
201
202    /**
203     * Checks if the given AST is annotated with at least one annotation that
204     * matches the given predicate and returns the AST representing the first
205     * matching annotation.
206     *
207     * <p>
208     * This method will not look for imports or package
209     * statements to detect the passed in annotation.
210     * </p>
211     *
212     * @param ast the current node
213     * @param predicate The predicate which decides if an annotation matches
214     * @return the AST representing that annotation
215     */
216    private static DetailAST findFirstAnnotation(final DetailAST ast,
217                                                 Predicate<DetailAST> predicate) {
218        final DetailAST holder = getAnnotationHolder(ast);
219        DetailAST result = null;
220        for (DetailAST child = holder.getFirstChild();
221            child != null; child = child.getNextSibling()) {
222            if (child.getType() == TokenTypes.ANNOTATION && predicate.test(child)) {
223                result = child;
224                break;
225            }
226        }
227
228        return result;
229    }
230
231}