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