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}