001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 * 043 * @throws UnsupportedOperationException if called 044 */ 045 private AnnotationUtil() { 046 throw new UnsupportedOperationException("do not instantiate."); 047 } 048 049 /** 050 * Checks if the AST is annotated with 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 return getAnnotation(ast, annotation) != null; 072 } 073 074 /** 075 * Checks if the AST is annotated with any annotation. 076 * 077 * @param ast the current node 078 * @return {@code true} if the AST contains at least one annotation 079 * @throws IllegalArgumentException when ast is null 080 */ 081 public static boolean containsAnnotation(final DetailAST ast) { 082 if (ast == null) { 083 throw new IllegalArgumentException(THE_AST_IS_NULL); 084 } 085 final DetailAST holder = getAnnotationHolder(ast); 086 return holder != null && holder.findFirstToken(TokenTypes.ANNOTATION) != null; 087 } 088 089 /** 090 * Checks if the given AST element is annotated with any of the specified annotations. 091 * 092 * <p> 093 * This method accepts both simple and fully-qualified names, 094 * e.g. "Override" will match both java.lang.Override and Override. 095 * </p> 096 * 097 * @param ast The type or method definition. 098 * @param annotations A collection of annotations to look for. 099 * @return {@code true} if the given AST element is annotated with 100 * at least one of the specified annotations; 101 * {@code false} otherwise. 102 * @throws IllegalArgumentException when ast or annotations are null 103 */ 104 public static boolean containsAnnotation(DetailAST ast, List<String> annotations) { 105 if (ast == null) { 106 throw new IllegalArgumentException(THE_AST_IS_NULL); 107 } 108 109 if (annotations == null) { 110 throw new IllegalArgumentException("annotations cannot be null"); 111 } 112 113 boolean result = false; 114 115 if (!annotations.isEmpty()) { 116 final DetailAST firstMatchingAnnotation = findFirstAnnotation(ast, annotationNode -> { 117 DetailAST identNode = annotationNode.findFirstToken(TokenTypes.IDENT); 118 if (identNode == null) { 119 identNode = annotationNode.findFirstToken(TokenTypes.DOT) 120 .findFirstToken(TokenTypes.IDENT); 121 } 122 123 return annotations.contains(identNode.getText()); 124 }); 125 result = firstMatchingAnnotation != null; 126 } 127 128 return result; 129 } 130 131 /** 132 * Gets the AST that holds a series of annotations for the 133 * potentially annotated AST. Returns {@code null} 134 * if the passed in AST does not have an Annotation Holder. 135 * 136 * @param ast the current node 137 * @return the Annotation Holder 138 * @throws IllegalArgumentException when ast is null 139 */ 140 public static DetailAST getAnnotationHolder(DetailAST ast) { 141 if (ast == null) { 142 throw new IllegalArgumentException(THE_AST_IS_NULL); 143 } 144 145 final DetailAST annotationHolder; 146 147 if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF 148 || ast.getType() == TokenTypes.PACKAGE_DEF) { 149 annotationHolder = ast.findFirstToken(TokenTypes.ANNOTATIONS); 150 } 151 else { 152 annotationHolder = ast.findFirstToken(TokenTypes.MODIFIERS); 153 } 154 155 return annotationHolder; 156 } 157 158 /** 159 * Checks if the AST is annotated with the passed in annotation 160 * and returns the AST representing that annotation. 161 * 162 * <p> 163 * This method will not look for imports or package 164 * statements to detect the passed in annotation. 165 * </p> 166 * 167 * <p> 168 * To check if an AST contains a passed in annotation 169 * taking into account fully-qualified names 170 * (ex: java.lang.Override, Override) 171 * this method will need to be called twice. Once for each 172 * name given. 173 * </p> 174 * 175 * @param ast the current node 176 * @param annotation the annotation name to check for 177 * @return the AST representing that annotation 178 * @throws IllegalArgumentException when ast or annotations are null; when annotation is blank 179 */ 180 public static DetailAST getAnnotation(final DetailAST ast, 181 String annotation) { 182 if (ast == null) { 183 throw new IllegalArgumentException(THE_AST_IS_NULL); 184 } 185 186 if (annotation == null) { 187 throw new IllegalArgumentException("the annotation is null"); 188 } 189 190 if (CommonUtil.isBlank(annotation)) { 191 throw new IllegalArgumentException( 192 "the annotation is empty or spaces"); 193 } 194 195 return findFirstAnnotation(ast, annotationNode -> { 196 final DetailAST firstChild = annotationNode.findFirstToken(TokenTypes.AT); 197 final String name = 198 FullIdent.createFullIdent(firstChild.getNextSibling()).getText(); 199 return annotation.equals(name); 200 }); 201 } 202 203 /** 204 * Checks if the given AST is annotated with at least one annotation that 205 * matches the given predicate and returns the AST representing the first 206 * matching annotation. 207 * 208 * <p> 209 * This method will not look for imports or package 210 * statements to detect the passed in annotation. 211 * </p> 212 * 213 * @param ast the current node 214 * @param predicate The predicate which decides if an annotation matches 215 * @return the AST representing that annotation 216 */ 217 private static DetailAST findFirstAnnotation(final DetailAST ast, 218 Predicate<DetailAST> predicate) { 219 final DetailAST holder = getAnnotationHolder(ast); 220 DetailAST result = null; 221 for (DetailAST child = holder.getFirstChild(); 222 child != null; child = child.getNextSibling()) { 223 if (child.getType() == TokenTypes.ANNOTATION && predicate.test(child)) { 224 result = child; 225 break; 226 } 227 } 228 229 return result; 230 } 231 232}