001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 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.checks.annotation; 021 022import org.apache.commons.lang3.ArrayUtils; 023 024import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 025import com.puppycrawl.tools.checkstyle.api.DetailAST; 026import com.puppycrawl.tools.checkstyle.api.TokenTypes; 027 028/** 029 * Check location of annotation on language elements. 030 * By default, Check enforce to locate annotations immediately after 031 * documentation block and before target element, annotation should be located 032 * on separate line from target element. 033 * 034 * <p> 035 * Example: 036 * </p> 037 * 038 * <pre> 039 * @Override 040 * @Nullable 041 * public String getNameIfPresent() { ... } 042 * </pre> 043 * 044 * <p> 045 * Check have following options: 046 * </p> 047 * <ul> 048 * <li>allowSamelineMultipleAnnotations - to allow annotation to be located on 049 * the same line as target element. Default value is false. 050 * </li> 051 * 052 * <li> 053 * allowSamelineSingleParameterlessAnnotation - to allow single parameterless 054 * annotation to be located on the same line as target element. Default value is false. 055 * </li> 056 * 057 * <li> 058 * allowSamelineParameterizedAnnotation - to allow parameterized annotation 059 * to be located on the same line as target element. Default value is false. 060 * </li> 061 * </ul> 062 * <br> 063 * <p> 064 * Example to allow single parameterless annotation on the same line: 065 * </p> 066 * <pre> 067 * @Override public int hashCode() { ... } 068 * </pre> 069 * 070 * <p>Use following configuration: 071 * <pre> 072 * <module name="AnnotationLocation"> 073 * <property name="allowSamelineMultipleAnnotations" value="false"/> 074 * <property name="allowSamelineSingleParameterlessAnnotation" 075 * value="true"/> 076 * <property name="allowSamelineParameterizedAnnotation" value="false" 077 * /> 078 * </module> 079 * </pre> 080 * <br> 081 * <p> 082 * Example to allow multiple parameterized annotations on the same line: 083 * </p> 084 * <pre> 085 * @SuppressWarnings("deprecation") @Mock DataLoader loader; 086 * </pre> 087 * 088 * <p>Use following configuration: 089 * <pre> 090 * <module name="AnnotationLocation"> 091 * <property name="allowSamelineMultipleAnnotations" value="true"/> 092 * <property name="allowSamelineSingleParameterlessAnnotation" 093 * value="true"/> 094 * <property name="allowSamelineParameterizedAnnotation" value="true" 095 * /> 096 * </module> 097 * </pre> 098 * <br> 099 * <p> 100 * Example to allow multiple parameterless annotations on the same line: 101 * </p> 102 * <pre> 103 * @Partial @Mock DataLoader loader; 104 * </pre> 105 * 106 * <p>Use following configuration: 107 * <pre> 108 * <module name="AnnotationLocation"> 109 * <property name="allowSamelineMultipleAnnotations" value="true"/> 110 * <property name="allowSamelineSingleParameterlessAnnotation" 111 * value="true"/> 112 * <property name="allowSamelineParameterizedAnnotation" value="false" 113 * /> 114 * </module> 115 * </pre> 116 * 117 * @author maxvetrenko 118 */ 119public class AnnotationLocationCheck extends AbstractCheck { 120 /** 121 * A key is pointing to the warning message text in "messages.properties" 122 * file. 123 */ 124 public static final String MSG_KEY_ANNOTATION_LOCATION_ALONE = "annotation.location.alone"; 125 126 /** 127 * A key is pointing to the warning message text in "messages.properties" 128 * file. 129 */ 130 public static final String MSG_KEY_ANNOTATION_LOCATION = "annotation.location"; 131 132 /** 133 * If true, it allows single prameterless annotation to be located on the same line as 134 * target element. 135 */ 136 private boolean allowSamelineSingleParameterlessAnnotation = true; 137 138 /** 139 * If true, it allows parameterized annotation to be located on the same line as 140 * target element. 141 */ 142 private boolean allowSamelineParameterizedAnnotation; 143 144 /** 145 * If true, it allows annotation to be located on the same line as 146 * target element. 147 */ 148 private boolean allowSamelineMultipleAnnotations; 149 150 /** 151 * Sets if allow same line single parameterless annotation. 152 * @param allow User's value of allowSamelineSingleParameterlessAnnotation. 153 */ 154 public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) { 155 allowSamelineSingleParameterlessAnnotation = allow; 156 } 157 158 /** 159 * Sets if allow parameterized annotation to be located on the same line as 160 * target element. 161 * @param allow User's value of allowSamelineParameterizedAnnotation. 162 */ 163 public final void setAllowSamelineParameterizedAnnotation(boolean allow) { 164 allowSamelineParameterizedAnnotation = allow; 165 } 166 167 /** 168 * Sets if allow annotation to be located on the same line as 169 * target element. 170 * @param allow User's value of allowSamelineMultipleAnnotations. 171 */ 172 public final void setAllowSamelineMultipleAnnotations(boolean allow) { 173 allowSamelineMultipleAnnotations = allow; 174 } 175 176 @Override 177 public int[] getDefaultTokens() { 178 return new int[] { 179 TokenTypes.CLASS_DEF, 180 TokenTypes.INTERFACE_DEF, 181 TokenTypes.ENUM_DEF, 182 TokenTypes.METHOD_DEF, 183 TokenTypes.CTOR_DEF, 184 TokenTypes.VARIABLE_DEF, 185 }; 186 } 187 188 @Override 189 public int[] getAcceptableTokens() { 190 return new int[] { 191 TokenTypes.CLASS_DEF, 192 TokenTypes.INTERFACE_DEF, 193 TokenTypes.ENUM_DEF, 194 TokenTypes.METHOD_DEF, 195 TokenTypes.CTOR_DEF, 196 TokenTypes.VARIABLE_DEF, 197 TokenTypes.PARAMETER_DEF, 198 TokenTypes.ANNOTATION_DEF, 199 TokenTypes.TYPECAST, 200 TokenTypes.LITERAL_THROWS, 201 TokenTypes.IMPLEMENTS_CLAUSE, 202 TokenTypes.TYPE_ARGUMENT, 203 TokenTypes.LITERAL_NEW, 204 TokenTypes.DOT, 205 TokenTypes.ANNOTATION_FIELD_DEF, 206 }; 207 } 208 209 @Override 210 public int[] getRequiredTokens() { 211 return ArrayUtils.EMPTY_INT_ARRAY; 212 } 213 214 @Override 215 public void visitToken(DetailAST ast) { 216 final DetailAST modifiersNode = ast.findFirstToken(TokenTypes.MODIFIERS); 217 218 if (hasAnnotations(modifiersNode)) { 219 checkAnnotations(modifiersNode, getAnnotationLevel(modifiersNode)); 220 } 221 } 222 223 /** 224 * Some javadoc. 225 * @param modifierNode Some javadoc. 226 * @param correctLevel Some javadoc. 227 */ 228 private void checkAnnotations(DetailAST modifierNode, int correctLevel) { 229 DetailAST annotation = modifierNode.getFirstChild(); 230 231 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 232 final boolean hasParameters = isParameterized(annotation); 233 234 if (!isCorrectLocation(annotation, hasParameters)) { 235 log(annotation.getLineNo(), 236 MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation)); 237 } 238 else if (annotation.getColumnNo() != correctLevel && !hasNodeBefore(annotation)) { 239 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION, 240 getAnnotationName(annotation), annotation.getColumnNo(), correctLevel); 241 } 242 annotation = annotation.getNextSibling(); 243 } 244 } 245 246 /** 247 * Some javadoc. 248 * @param annotation Some javadoc. 249 * @param hasParams Some javadoc. 250 * @return Some javadoc. 251 */ 252 private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { 253 final boolean allowingCondition; 254 255 if (hasParams) { 256 allowingCondition = allowSamelineParameterizedAnnotation; 257 } 258 else { 259 allowingCondition = allowSamelineSingleParameterlessAnnotation; 260 } 261 return allowSamelineMultipleAnnotations 262 || allowingCondition && !hasNodeBefore(annotation) 263 || !allowingCondition && !hasNodeBeside(annotation); 264 } 265 266 /** 267 * Some javadoc. 268 * @param annotation Some javadoc. 269 * @return Some javadoc. 270 */ 271 private static String getAnnotationName(DetailAST annotation) { 272 DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT); 273 if (identNode == null) { 274 identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT); 275 } 276 return identNode.getText(); 277 } 278 279 /** 280 * Some javadoc. 281 * @param annotation Some javadoc. 282 * @return Some javadoc. 283 */ 284 private static boolean hasNodeAfter(DetailAST annotation) { 285 final int annotationLineNo = annotation.getLineNo(); 286 DetailAST nextNode = annotation.getNextSibling(); 287 288 if (nextNode == null) { 289 nextNode = annotation.getParent().getNextSibling(); 290 } 291 292 return annotationLineNo == nextNode.getLineNo(); 293 } 294 295 /** 296 * Some javadoc. 297 * @param annotation Some javadoc. 298 * @return Some javadoc. 299 */ 300 private static boolean hasNodeBefore(DetailAST annotation) { 301 final int annotationLineNo = annotation.getLineNo(); 302 final DetailAST previousNode = annotation.getPreviousSibling(); 303 304 return previousNode != null && annotationLineNo == previousNode.getLineNo(); 305 } 306 307 /** 308 * Some javadoc. 309 * @param annotation Some javadoc. 310 * @return Some javadoc. 311 */ 312 private static boolean hasNodeBeside(DetailAST annotation) { 313 return hasNodeBefore(annotation) || hasNodeAfter(annotation); 314 } 315 316 /** 317 * Some javadoc. 318 * @param modifierNode Some javadoc. 319 * @return Some javadoc. 320 */ 321 private static int getAnnotationLevel(DetailAST modifierNode) { 322 return modifierNode.getParent().getColumnNo(); 323 } 324 325 /** 326 * Some javadoc. 327 * @param annotation Some javadoc. 328 * @return Some javadoc. 329 */ 330 private static boolean isParameterized(DetailAST annotation) { 331 return annotation.findFirstToken(TokenTypes.EXPR) != null; 332 } 333 334 /** 335 * Some javadoc. 336 * @param modifierNode Some javadoc. 337 * @return Some javadoc. 338 */ 339 private static boolean hasAnnotations(DetailAST modifierNode) { 340 return modifierNode.findFirstToken(TokenTypes.ANNOTATION) != null; 341 } 342}