001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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.Check; 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 Check { 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 * Some javadoc. 134 */ 135 private boolean allowSamelineSingleParameterlessAnnotation = true; 136 137 /** 138 * Some javadoc. 139 */ 140 private boolean allowSamelineParameterizedAnnotation; 141 142 /** 143 * Some javadoc. 144 */ 145 private boolean allowSamelineMultipleAnnotations; 146 147 /** 148 * Some javadoc. 149 * @param allow Some javadoc. 150 */ 151 public final void setAllowSamelineSingleParameterlessAnnotation(boolean allow) { 152 allowSamelineSingleParameterlessAnnotation = allow; 153 } 154 155 /** 156 * Some javadoc. 157 * @param allow Some javadoc. 158 */ 159 public final void setAllowSamelineParameterizedAnnotation(boolean allow) { 160 allowSamelineParameterizedAnnotation = allow; 161 } 162 163 /** 164 * Some javadoc. 165 * @param allow Some javadoc. 166 */ 167 public final void setAllowSamelineMultipleAnnotations(boolean allow) { 168 allowSamelineMultipleAnnotations = allow; 169 } 170 171 @Override 172 public int[] getDefaultTokens() { 173 return new int[] { 174 TokenTypes.CLASS_DEF, 175 TokenTypes.INTERFACE_DEF, 176 TokenTypes.ENUM_DEF, 177 TokenTypes.METHOD_DEF, 178 TokenTypes.CTOR_DEF, 179 TokenTypes.VARIABLE_DEF, 180 }; 181 } 182 183 @Override 184 public int[] getAcceptableTokens() { 185 return new int[] { 186 TokenTypes.CLASS_DEF, 187 TokenTypes.INTERFACE_DEF, 188 TokenTypes.ENUM_DEF, 189 TokenTypes.METHOD_DEF, 190 TokenTypes.CTOR_DEF, 191 TokenTypes.VARIABLE_DEF, 192 TokenTypes.PARAMETER_DEF, 193 TokenTypes.ANNOTATION_DEF, 194 TokenTypes.TYPECAST, 195 TokenTypes.LITERAL_THROWS, 196 TokenTypes.IMPLEMENTS_CLAUSE, 197 TokenTypes.TYPE_ARGUMENT, 198 TokenTypes.LITERAL_NEW, 199 TokenTypes.DOT, 200 TokenTypes.ANNOTATION_FIELD_DEF, 201 }; 202 } 203 204 @Override 205 public int[] getRequiredTokens() { 206 return ArrayUtils.EMPTY_INT_ARRAY; 207 } 208 209 @Override 210 public void visitToken(DetailAST ast) { 211 final DetailAST modifiersNode = ast.findFirstToken(TokenTypes.MODIFIERS); 212 213 if (hasAnnotations(modifiersNode)) { 214 checkAnnotations(modifiersNode, getAnnotationLevel(modifiersNode)); 215 } 216 } 217 218 /** 219 * Some javadoc. 220 * @param modifierNode Some javadoc. 221 * @param correctLevel Some javadoc. 222 */ 223 private void checkAnnotations(DetailAST modifierNode, int correctLevel) { 224 DetailAST annotation = modifierNode.getFirstChild(); 225 226 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 227 final boolean hasParameters = isParameterized(annotation); 228 229 if (!isCorrectLocation(annotation, hasParameters)) { 230 log(annotation.getLineNo(), 231 MSG_KEY_ANNOTATION_LOCATION_ALONE, getAnnotationName(annotation)); 232 } 233 else if (annotation.getColumnNo() != correctLevel && !hasNodeBefore(annotation)) { 234 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_LOCATION, 235 getAnnotationName(annotation), annotation.getColumnNo(), correctLevel); 236 } 237 annotation = annotation.getNextSibling(); 238 } 239 } 240 241 /** 242 * Some javadoc. 243 * @param annotation Some javadoc. 244 * @param hasParams Some javadoc. 245 * @return Some javadoc. 246 */ 247 private boolean isCorrectLocation(DetailAST annotation, boolean hasParams) { 248 final boolean allowingCondition; 249 250 if (hasParams) { 251 allowingCondition = allowSamelineParameterizedAnnotation; 252 } 253 else { 254 allowingCondition = allowSamelineSingleParameterlessAnnotation; 255 } 256 return allowingCondition && !hasNodeBefore(annotation) 257 || !allowingCondition && !hasNodeBeside(annotation) 258 || allowSamelineMultipleAnnotations; 259 } 260 261 /** 262 * Some javadoc. 263 * @param annotation Some javadoc. 264 * @return Some javadoc. 265 */ 266 private static String getAnnotationName(DetailAST annotation) { 267 DetailAST identNode = annotation.findFirstToken(TokenTypes.IDENT); 268 if (identNode == null) { 269 identNode = annotation.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT); 270 } 271 return identNode.getText(); 272 } 273 274 /** 275 * Some javadoc. 276 * @param annotation Some javadoc. 277 * @return Some javadoc. 278 */ 279 private static boolean hasNodeAfter(DetailAST annotation) { 280 final int annotationLineNo = annotation.getLineNo(); 281 DetailAST nextNode = annotation.getNextSibling(); 282 283 if (nextNode == null) { 284 nextNode = annotation.getParent().getNextSibling(); 285 } 286 287 return annotationLineNo == nextNode.getLineNo(); 288 } 289 290 /** 291 * Some javadoc. 292 * @param annotation Some javadoc. 293 * @return Some javadoc. 294 */ 295 private static boolean hasNodeBefore(DetailAST annotation) { 296 final int annotationLineNo = annotation.getLineNo(); 297 final DetailAST previousNode = annotation.getPreviousSibling(); 298 299 return previousNode != null && annotationLineNo == previousNode.getLineNo(); 300 } 301 302 /** 303 * Some javadoc. 304 * @param annotation Some javadoc. 305 * @return Some javadoc. 306 */ 307 private static boolean hasNodeBeside(DetailAST annotation) { 308 return hasNodeBefore(annotation) || hasNodeAfter(annotation); 309 } 310 311 /** 312 * Some javadoc. 313 * @param modifierNode Some javadoc. 314 * @return Some javadoc. 315 */ 316 private static int getAnnotationLevel(DetailAST modifierNode) { 317 return modifierNode.getParent().getColumnNo(); 318 } 319 320 /** 321 * Some javadoc. 322 * @param annotation Some javadoc. 323 * @return Some javadoc. 324 */ 325 private static boolean isParameterized(DetailAST annotation) { 326 return annotation.findFirstToken(TokenTypes.EXPR) != null; 327 } 328 329 /** 330 * Some javadoc. 331 * @param modifierNode Some javadoc. 332 * @return Some javadoc. 333 */ 334 private static boolean hasAnnotations(DetailAST modifierNode) { 335 return modifierNode.findFirstToken(TokenTypes.ANNOTATION) != null; 336 } 337}