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