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