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