001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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 com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.FullIdent;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025
026/**
027 * Contains utility methods designed to work with annotations.
028 *
029 * @author Travis Schneeberger
030 */
031public final class AnnotationUtility {
032
033    /**
034     * Common message.
035     */
036    private static final String THE_AST_IS_NULL = "the ast is null";
037
038    /**
039     * Private utility constructor.
040     * @throws UnsupportedOperationException if called
041     */
042    private AnnotationUtility() {
043        throw new UnsupportedOperationException("do not instantiate.");
044    }
045
046    /**
047     * Checks to see if the AST is annotated with
048     * the passed in annotation.
049     *
050     * <p>
051     * This method will not look for imports or package
052     * statements to detect the passed in annotation.
053     * </p>
054     *
055     * <p>
056     * To check if an AST contains a passed in annotation
057     * taking into account fully-qualified names
058     * (ex: java.lang.Override, Override)
059     * this method will need to be called twice. Once for each
060     * name given.
061     * </p>
062     *
063     * @param ast the current node
064     * @param annotation the annotation name to check for
065     * @return true if contains the annotation
066     */
067    public static boolean containsAnnotation(final DetailAST ast,
068        String annotation) {
069        if (ast == null) {
070            throw new IllegalArgumentException(THE_AST_IS_NULL);
071        }
072        return getAnnotation(ast, annotation) != null;
073    }
074
075    /**
076     * Checks to see if the AST is annotated with
077     * any annotation.
078     *
079     * @param ast the current node
080     * @return true if contains an annotation
081     */
082    public static boolean containsAnnotation(final DetailAST ast) {
083        if (ast == null) {
084            throw new IllegalArgumentException(THE_AST_IS_NULL);
085        }
086        final DetailAST holder = getAnnotationHolder(ast);
087        return holder != null && holder.findFirstToken(TokenTypes.ANNOTATION) != null;
088    }
089
090    /**
091     * Gets the AST that holds a series of annotations for the
092     * potentially annotated AST.  Returns {@code null}
093     * the passed in AST is not have an Annotation Holder.
094     *
095     * @param ast the current node
096     * @return the Annotation Holder
097     */
098    public static DetailAST getAnnotationHolder(DetailAST ast) {
099        if (ast == null) {
100            throw new IllegalArgumentException(THE_AST_IS_NULL);
101        }
102
103        final DetailAST annotationHolder;
104
105        if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF
106            || ast.getType() == TokenTypes.PACKAGE_DEF) {
107            annotationHolder = ast.findFirstToken(TokenTypes.ANNOTATIONS);
108        }
109        else {
110            annotationHolder = ast.findFirstToken(TokenTypes.MODIFIERS);
111        }
112
113        return annotationHolder;
114    }
115
116    /**
117     * Checks to see if the AST is annotated with
118     * the passed in annotation and return the AST
119     * representing that annotation.
120     *
121     * <p>
122     * This method will not look for imports or package
123     * statements to detect the passed in annotation.
124     * </p>
125     *
126     * <p>
127     * To check if an AST contains a passed in annotation
128     * taking into account fully-qualified names
129     * (ex: java.lang.Override, Override)
130     * this method will need to be called twice. Once for each
131     * name given.
132     * </p>
133     *
134     * @param ast the current node
135     * @param annotation the annotation name to check for
136     * @return the AST representing that annotation
137     */
138    public static DetailAST getAnnotation(final DetailAST ast,
139        String annotation) {
140        if (ast == null) {
141            throw new IllegalArgumentException(THE_AST_IS_NULL);
142        }
143
144        if (annotation == null) {
145            throw new IllegalArgumentException("the annotation is null");
146        }
147
148        if (CommonUtils.isBlank(annotation)) {
149            throw new IllegalArgumentException(
150                    "the annotation is empty or spaces");
151        }
152
153        final DetailAST holder = getAnnotationHolder(ast);
154        DetailAST result = null;
155        for (DetailAST child = holder.getFirstChild();
156            child != null; child = child.getNextSibling()) {
157            if (child.getType() == TokenTypes.ANNOTATION) {
158                final DetailAST firstChild = child.findFirstToken(TokenTypes.AT);
159                final String name =
160                    FullIdent.createFullIdent(firstChild.getNextSibling()).getText();
161                if (annotation.equals(name)) {
162                    result = child;
163                    break;
164                }
165            }
166        }
167
168        return result;
169    }
170
171}