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 java.util.Locale; 023 024import org.apache.commons.beanutils.ConversionException; 025 026import com.puppycrawl.tools.checkstyle.api.Check; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029 030/** 031 * This check controls the style with the usage of annotations. 032 * 033 * <p>Annotations have three element styles starting with the least verbose. 034 * <ul> 035 * <li>{@link ElementStyle#COMPACT_NO_ARRAY COMPACT_NO_ARRAY}</li> 036 * <li>{@link ElementStyle#COMPACT COMPACT}</li> 037 * <li>{@link ElementStyle#EXPANDED EXPANDED}</li> 038 * </ul> 039 * To not enforce an element style 040 * a {@link ElementStyle#IGNORE IGNORE} type is provided. The desired style 041 * can be set through the {@code elementStyle} property. 042 * 043 * <p>Using the EXPANDED style is more verbose. The expanded version 044 * is sometimes referred to as "named parameters" in other languages. 045 * 046 * <p>Using the COMPACT style is less verbose. This style can only 047 * be used when there is an element called 'value' which is either 048 * the sole element or all other elements have default values. 049 * 050 * <p>Using the COMPACT_NO_ARRAY style is less verbose. It is similar 051 * to the COMPACT style but single value arrays are flagged. With 052 * annotations a single value array does not need to be placed in an 053 * array initializer. This style can only be used when there is an 054 * element called 'value' which is either the sole element or all other 055 * elements have default values. 056 * 057 * <p>The ending parenthesis are optional when using annotations with no elements. 058 * To always require ending parenthesis use the 059 * {@link ClosingParens#ALWAYS ALWAYS} type. To never have ending parenthesis 060 * use the {@link ClosingParens#NEVER NEVER} type. To not enforce a 061 * closing parenthesis preference a {@link ClosingParens#IGNORE IGNORE} type is 062 * provided. Set this through the {@code closingParens} property. 063 * 064 * <p>Annotations also allow you to specify arrays of elements in a standard 065 * format. As with normal arrays, a trailing comma is optional. To always 066 * require a trailing comma use the {@link TrailingArrayComma#ALWAYS ALWAYS} 067 * type. To never have a trailing comma use the 068 * {@link TrailingArrayComma#NEVER NEVER} type. To not enforce a trailing 069 * array comma preference a {@link TrailingArrayComma#IGNORE IGNORE} type 070 * is provided. Set this through the {@code trailingArrayComma} property. 071 * 072 * <p>By default the ElementStyle is set to EXPANDED, the TrailingArrayComma 073 * is set to NEVER, and the ClosingParens is set to ALWAYS. 074 * 075 * <p>According to the JLS, it is legal to include a trailing comma 076 * in arrays used in annotations but Sun's Java 5 & 6 compilers will not 077 * compile with this syntax. This may in be a bug in Sun's compilers 078 * since eclipse 3.4's built-in compiler does allow this syntax as 079 * defined in the JLS. Note: this was tested with compilers included with 080 * JDK versions 1.5.0.17 and 1.6.0.11 and the compiler included with eclipse 081 * 3.4.1. 082 * 083 * <p>See <a 084 * href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.7"> 085 * Java Language specification, §9.7</a>. 086 * 087 * <p>An example shown below is set to enforce an EXPANDED style, with a 088 * trailing array comma set to NEVER and always including the closing 089 * parenthesis. 090 * 091 * <pre> 092 * <module name="AnnotationUseStyle"> 093 * <property name="ElementStyle" 094 * value="EXPANDED"/> 095 * <property name="TrailingArrayComma" 096 * value="NEVER"/> 097 * <property name="ClosingParens" 098 * value="ALWAYS"/> 099 * </module> 100 * </pre> 101 * 102 * @author Travis Schneeberger 103 */ 104public final class AnnotationUseStyleCheck extends Check { 105 /** 106 * A key is pointing to the warning message text in "messages.properties" 107 * file. 108 */ 109 public static final String MSG_KEY_ANNOTATION_INCORRECT_STYLE = 110 "annotation.incorrect.style"; 111 112 /** 113 * A key is pointing to the warning message text in "messages.properties" 114 * file. 115 */ 116 public static final String MSG_KEY_ANNOTATION_PARENS_MISSING = 117 "annotation.parens.missing"; 118 119 /** 120 * A key is pointing to the warning message text in "messages.properties" 121 * file. 122 */ 123 public static final String MSG_KEY_ANNOTATION_PARENS_PRESENT = 124 "annotation.parens.present"; 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_TRAILING_COMMA_MISSING = 131 "annotation.trailing.comma.missing"; 132 133 /** 134 * A key is pointing to the warning message text in "messages.properties" 135 * file. 136 */ 137 public static final String MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT = 138 "annotation.trailing.comma.present"; 139 140 /** 141 * The element name used to receive special linguistic support 142 * for annotation use. 143 */ 144 private static final String ANNOTATION_ELEMENT_SINGLE_NAME = 145 "value"; 146 147 //not extending AbstractOptionCheck because check 148 //has more than one option type. 149 150 /** 151 * ElementStyle option. 152 * @see #setElementStyle(String) 153 */ 154 private ElementStyle elementStyle = ElementStyle.COMPACT_NO_ARRAY; 155 156 //defaulting to NEVER because of the strange compiler behavior 157 /** 158 * Trailing array comma option. 159 * @see #setTrailingArrayComma(String) 160 */ 161 private TrailingArrayComma trailingArrayComma = TrailingArrayComma.NEVER; 162 163 /** 164 * Closing parens option. 165 * @see #setClosingParens(String) 166 */ 167 private ClosingParens closingParens = ClosingParens.NEVER; 168 169 /** 170 * Sets the ElementStyle from a string. 171 * 172 * @param style string representation 173 * @throws ConversionException if cannot convert string. 174 */ 175 public void setElementStyle(final String style) { 176 elementStyle = getOption(ElementStyle.class, style); 177 } 178 179 /** 180 * Sets the TrailingArrayComma from a string. 181 * 182 * @param comma string representation 183 * @throws ConversionException if cannot convert string. 184 */ 185 public void setTrailingArrayComma(final String comma) { 186 trailingArrayComma = getOption(TrailingArrayComma.class, comma); 187 } 188 189 /** 190 * Sets the ClosingParens from a string. 191 * 192 * @param parens string representation 193 * @throws ConversionException if cannot convert string. 194 */ 195 public void setClosingParens(final String parens) { 196 closingParens = getOption(ClosingParens.class, parens); 197 } 198 199 /** 200 * Retrieves an {@link Enum Enum} type from a @{link String String}. 201 * @param <T> the enum type 202 * @param enumClass the enum class 203 * @param value the string representing the enum 204 * @return the enum type 205 */ 206 private static <T extends Enum<T>> T getOption(final Class<T> enumClass, 207 final String value) { 208 try { 209 return Enum.valueOf(enumClass, value.trim().toUpperCase(Locale.ENGLISH)); 210 } 211 catch (final IllegalArgumentException iae) { 212 throw new ConversionException("unable to parse " + value, iae); 213 } 214 } 215 216 @Override 217 public int[] getDefaultTokens() { 218 return getRequiredTokens(); 219 } 220 221 @Override 222 public int[] getRequiredTokens() { 223 return new int[] { 224 TokenTypes.ANNOTATION, 225 }; 226 } 227 228 @Override 229 public int[] getAcceptableTokens() { 230 return getRequiredTokens(); 231 } 232 233 @Override 234 public void visitToken(final DetailAST ast) { 235 checkStyleType(ast); 236 checkCheckClosingParens(ast); 237 checkTrailingComma(ast); 238 } 239 240 /** 241 * Checks to see if the 242 * {@link ElementStyle AnnotationElementStyle} 243 * is correct. 244 * 245 * @param annotation the annotation token 246 */ 247 private void checkStyleType(final DetailAST annotation) { 248 249 switch (elementStyle) { 250 case COMPACT_NO_ARRAY: 251 checkCompactNoArrayStyle(annotation); 252 break; 253 case COMPACT: 254 checkCompactStyle(annotation); 255 break; 256 case EXPANDED: 257 checkExpandedStyle(annotation); 258 break; 259 case IGNORE: 260 default: 261 break; 262 } 263 } 264 265 /** 266 * Checks for expanded style type violations. 267 * 268 * @param annotation the annotation token 269 */ 270 private void checkExpandedStyle(final DetailAST annotation) { 271 final int valuePairCount = 272 annotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 273 274 if (valuePairCount == 0 275 && annotation.branchContains(TokenTypes.EXPR)) { 276 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, 277 ElementStyle.EXPANDED); 278 } 279 } 280 281 /** 282 * Checks for compact style type violations. 283 * 284 * @param annotation the annotation token 285 */ 286 private void checkCompactStyle(final DetailAST annotation) { 287 final int valuePairCount = 288 annotation.getChildCount( 289 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 290 291 final DetailAST valuePair = 292 annotation.findFirstToken( 293 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 294 295 if (valuePairCount == 1 296 && ANNOTATION_ELEMENT_SINGLE_NAME.equals( 297 valuePair.getFirstChild().getText())) { 298 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, 299 ElementStyle.COMPACT); 300 } 301 } 302 303 /** 304 * Checks for compact no array style type violations. 305 * 306 * @param annotation the annotation token 307 */ 308 private void checkCompactNoArrayStyle(final DetailAST annotation) { 309 final DetailAST arrayInit = 310 annotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 311 312 final int valuePairCount = 313 annotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 314 315 final DetailAST valuePair = 316 annotation.findFirstToken(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 317 318 //in compact style with one value 319 if (arrayInit != null 320 && arrayInit.getChildCount(TokenTypes.EXPR) == 1) { 321 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, 322 ElementStyle.COMPACT_NO_ARRAY); 323 } 324 //in expanded style with one value and the correct element name 325 else if (valuePairCount == 1) { 326 final DetailAST nestedArrayInit = 327 valuePair.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 328 329 if (nestedArrayInit != null 330 && ANNOTATION_ELEMENT_SINGLE_NAME.equals( 331 valuePair.getFirstChild().getText()) 332 && nestedArrayInit.getChildCount(TokenTypes.EXPR) == 1) { 333 log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, 334 ElementStyle.COMPACT_NO_ARRAY); 335 } 336 } 337 } 338 339 /** 340 * Checks to see if the trailing comma is present if required or 341 * prohibited. 342 * 343 * @param annotation the annotation token 344 */ 345 private void checkTrailingComma(final DetailAST annotation) { 346 if (trailingArrayComma == TrailingArrayComma.IGNORE) { 347 return; 348 } 349 350 DetailAST child = annotation.getFirstChild(); 351 352 while (child != null) { 353 DetailAST arrayInit = null; 354 355 if (child.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 356 arrayInit = child.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 357 } 358 else if (child.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) { 359 arrayInit = child; 360 } 361 362 if (arrayInit != null) { 363 logCommaViolation(arrayInit); 364 } 365 child = child.getNextSibling(); 366 } 367 } 368 369 /** 370 * Logs a trailing array comma violation if one exists. 371 * 372 * @param ast the array init 373 * {@link TokenTypes#ANNOTATION_ARRAY_INIT ANNOTATION_ARRAY_INIT}. 374 */ 375 private void logCommaViolation(final DetailAST ast) { 376 final DetailAST rCurly = ast.findFirstToken(TokenTypes.RCURLY); 377 378 //comma can be null if array is empty 379 final DetailAST comma = rCurly.getPreviousSibling(); 380 381 if (trailingArrayComma == TrailingArrayComma.ALWAYS 382 && (comma == null || comma.getType() != TokenTypes.COMMA)) { 383 log(rCurly.getLineNo(), 384 rCurly.getColumnNo(), MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING); 385 } 386 else if (trailingArrayComma == TrailingArrayComma.NEVER 387 && comma != null && comma.getType() == TokenTypes.COMMA) { 388 log(comma.getLineNo(), 389 comma.getColumnNo(), MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT); 390 } 391 } 392 393 /** 394 * Checks to see if the closing parenthesis are present if required or 395 * prohibited. 396 * 397 * @param ast the annotation token 398 */ 399 private void checkCheckClosingParens(final DetailAST ast) { 400 if (closingParens == ClosingParens.IGNORE) { 401 return; 402 } 403 404 final DetailAST paren = ast.getLastChild(); 405 final boolean parenExists = paren.getType() == TokenTypes.RPAREN; 406 407 if (closingParens == ClosingParens.ALWAYS 408 && !parenExists) { 409 log(ast.getLineNo(), MSG_KEY_ANNOTATION_PARENS_MISSING); 410 } 411 else if (closingParens == ClosingParens.NEVER 412 && !ast.branchContains(TokenTypes.EXPR) 413 && !ast.branchContains(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) 414 && !ast.branchContains(TokenTypes.ANNOTATION_ARRAY_INIT) 415 && parenExists) { 416 log(ast.getLineNo(), MSG_KEY_ANNOTATION_PARENS_PRESENT); 417 } 418 } 419 420 /** 421 * Defines the styles for defining elements in an annotation. 422 * @author Travis Schneeberger 423 */ 424 public enum ElementStyle { 425 426 /** 427 * Expanded example 428 * 429 * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. 430 */ 431 EXPANDED, 432 433 /** 434 * Compact example 435 * 436 * <pre>@SuppressWarnings({"unchecked","unused",})</pre> 437 * <br>or<br> 438 * <pre>@SuppressWarnings("unchecked")</pre>. 439 */ 440 COMPACT, 441 442 /** 443 * Compact example.] 444 * 445 * <pre>@SuppressWarnings("unchecked")</pre>. 446 */ 447 COMPACT_NO_ARRAY, 448 449 /** 450 * Mixed styles. 451 */ 452 IGNORE, 453 } 454 455 /** 456 * Defines the two styles for defining 457 * elements in an annotation. 458 * 459 * @author Travis Schneeberger 460 */ 461 public enum TrailingArrayComma { 462 463 /** 464 * With comma example 465 * 466 * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. 467 */ 468 ALWAYS, 469 470 /** 471 * Without comma example 472 * 473 * <pre>@SuppressWarnings(value={"unchecked","unused"})</pre>. 474 */ 475 NEVER, 476 477 /** 478 * Mixed styles. 479 */ 480 IGNORE, 481 } 482 483 /** 484 * Defines the two styles for defining 485 * elements in an annotation. 486 * 487 * @author Travis Schneeberger 488 */ 489 public enum ClosingParens { 490 491 /** 492 * With parens example 493 * 494 * <pre>@Deprecated()</pre>. 495 */ 496 ALWAYS, 497 498 /** 499 * Without parens example 500 * 501 * <pre>@Deprecated</pre>. 502 */ 503 NEVER, 504 505 /** 506 * Mixed styles. 507 */ 508 IGNORE, 509 } 510}