001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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     */
079    public static boolean containsAnnotation(final DetailAST ast) {
080        if (ast == null) {
081            throw new IllegalArgumentException(THE_AST_IS_NULL);
082        }
083        final DetailAST holder = getAnnotationHolder(ast);
084        return holder != null && holder.findFirstToken(TokenTypes.ANNOTATION) != null;
085    }
086
087    /**
088     * Checks if the given AST element is annotated with any of the specified annotations.
089     *
090     * <p>
091     * This method accepts both simple and fully-qualified names,
092     * e.g. "Override" will match both java.lang.Override and Override.
093     * </p>
094     *
095     * @param ast The type or method definition.
096     * @param annotations A collection of annotations to look for.
097     * @return {@code true} if the given AST element is annotated with
098     *                      at least one of the specified annotations;
099     *                      {@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}