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