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