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