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 //not extending AbstractOptionCheck because check 129 //has more than one option type. 130 131 /** @see #setElementStyle(String) */ 132 private ElementStyle mStyle = ElementStyle.COMPACT_NO_ARRAY; 133 134 //defaulting to NEVER because of the strange compiler behavior 135 /** @see #setTrailingArrayComma(String) */ 136 private TrailingArrayComma mComma = TrailingArrayComma.NEVER; 137 138 /** @see #setClosingParens(String) */ 139 private ClosingParens mParens = ClosingParens.NEVER; 140 141 /** 142 * Sets the ElementStyle from a string. 143 * 144 * @param aStyle string representation 145 * @throws ConversionException if cannot convert string. 146 */ 147 public void setElementStyle(final String aStyle) 148 { 149 this.mStyle = this.getOption(ElementStyle.class, aStyle); 150 } 151 152 /** 153 * Sets the TrailingArrayComma from a string. 154 * 155 * @param aComma string representation 156 * @throws ConversionException if cannot convert string. 157 */ 158 public void setTrailingArrayComma(final String aComma) 159 { 160 this.mComma = this.getOption(TrailingArrayComma.class, aComma); 161 } 162 163 /** 164 * Sets the ClosingParens from a string. 165 * 166 * @param aParens string representation 167 * @throws ConversionException if cannot convert string. 168 */ 169 public void setClosingParens(final String aParens) 170 { 171 this.mParens = this.getOption(ClosingParens.class, aParens); 172 } 173 174 /** 175 * Retrieves an {@link Enum Enum} type from a @{link String String}. 176 * @param <T> the enum type 177 * @param aEnumClass the enum class 178 * @param aString the string representing the enum 179 * @return the enum type 180 */ 181 private <T extends Enum<T>> T getOption(final Class<T> aEnumClass, 182 final String aString) 183 { 184 try { 185 return Enum.valueOf(aEnumClass, aString.trim().toUpperCase()); 186 } 187 catch (final IllegalArgumentException iae) { 188 throw new ConversionException("unable to parse " + aString, iae); 189 } 190 } 191 192 /** {@inheritDoc} */ 193 @Override 194 public int[] getDefaultTokens() 195 { 196 return this.getRequiredTokens(); 197 } 198 199 /** {@inheritDoc} */ 200 @Override 201 public int[] getRequiredTokens() 202 { 203 return new int[] { 204 TokenTypes.ANNOTATION, 205 }; 206 } 207 208 /** {@inheritDoc} */ 209 @Override 210 public int[] getAcceptableTokens() 211 { 212 return this.getRequiredTokens(); 213 } 214 215 /** {@inheritDoc} */ 216 @Override 217 public void visitToken(final DetailAST aAST) 218 { 219 this.checkStyleType(aAST); 220 this.checkCheckClosingParens(aAST); 221 this.checkTrailingComma(aAST); 222 } 223 224 /** 225 * Checks to see if the 226 * {@link ElementStyle AnnotationElementStyle} 227 * is correct. 228 * 229 * @param aAnnotation the annotation token 230 */ 231 private void checkStyleType(final DetailAST aAnnotation) 232 { 233 if (ElementStyle.IGNORE.equals(this.mStyle) 234 || this.mStyle == null) 235 { 236 return; 237 } 238 239 if (ElementStyle.COMPACT_NO_ARRAY.equals(this.mStyle)) { 240 this.checkCompactNoArrayStyle(aAnnotation); 241 } 242 else if (ElementStyle.COMPACT.equals(this.mStyle)) { 243 this.checkCompactStyle(aAnnotation); 244 } 245 else if (ElementStyle.EXPANDED.equals(this.mStyle)) { 246 this.checkExpandedStyle(aAnnotation); 247 } 248 } 249 250 /** 251 * Checks for expanded style type violations. 252 * 253 * @param aAnnotation the annotation token 254 */ 255 private void checkExpandedStyle(final DetailAST aAnnotation) 256 { 257 final int valuePairCount = 258 aAnnotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 259 260 if (valuePairCount == 0 261 && aAnnotation.branchContains(TokenTypes.EXPR)) 262 { 263 this.log(aAnnotation.getLineNo(), "annotation.incorrect.style", 264 ElementStyle.EXPANDED); 265 } 266 } 267 268 /** 269 * Checks for compact style type violations. 270 * 271 * @param aAnnotation the annotation token 272 */ 273 private void checkCompactStyle(final DetailAST aAnnotation) 274 { 275 final int valuePairCount = 276 aAnnotation.getChildCount( 277 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 278 279 final DetailAST valuePair = 280 aAnnotation.findFirstToken( 281 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 282 283 if (valuePairCount == 1 284 && AnnotationUseStyleCheck.ANNOTATION_ELEMENT_SINGLE_NAME.equals( 285 valuePair.getFirstChild().getText())) 286 { 287 this.log(aAnnotation.getLineNo(), "annotation.incorrect.style", 288 ElementStyle.COMPACT); 289 } 290 } 291 292 /** 293 * Checks for compact no array style type violations. 294 * 295 * @param aAnnotation the annotation token 296 */ 297 private void checkCompactNoArrayStyle(final DetailAST aAnnotation) 298 { 299 final DetailAST arrayInit = 300 aAnnotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 301 302 final int valuePairCount = 303 aAnnotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 304 305 final DetailAST valuePair = 306 aAnnotation.findFirstToken(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); 307 308 //in compact style with one value 309 if (arrayInit != null 310 && arrayInit.getChildCount(TokenTypes.EXPR) == 1) 311 { 312 this.log(aAnnotation.getLineNo(), "annotation.incorrect.style", 313 ElementStyle.COMPACT_NO_ARRAY); 314 } 315 //in expanded style with one value and the correct element name 316 else if (valuePairCount == 1) { 317 final DetailAST nestedArrayInit = 318 valuePair.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 319 320 if (nestedArrayInit != null 321 && AnnotationUseStyleCheck. 322 ANNOTATION_ELEMENT_SINGLE_NAME.equals( 323 valuePair.getFirstChild().getText()) 324 && nestedArrayInit.getChildCount(TokenTypes.EXPR) == 1) 325 { 326 this.log(aAnnotation.getLineNo(), "annotation.incorrect.style", 327 ElementStyle.COMPACT_NO_ARRAY); 328 } 329 } 330 } 331 332 /** 333 * Checks to see if the trailing comma is present if required or 334 * prohibited. 335 * 336 * @param aAnnotation the annotation token 337 */ 338 private void checkTrailingComma(final DetailAST aAnnotation) 339 { 340 if (TrailingArrayComma.IGNORE.equals(this.mComma) 341 || this.mComma == null) 342 { 343 return; 344 } 345 346 DetailAST child = aAnnotation.getFirstChild(); 347 348 while (child != null) { 349 DetailAST arrayInit = null; 350 351 if (child.getType() 352 == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) 353 { 354 arrayInit = 355 child.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); 356 } 357 else if (child.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) { 358 arrayInit = child; 359 } 360 361 if (arrayInit != null) { 362 this.logCommaViolation(arrayInit); 363 } 364 child = child.getNextSibling(); 365 } 366 } 367 368 /** 369 * logs a trailing array comma violation if one exists. 370 * 371 * @param aAST the array init 372 * {@link TokenTypes#ANNOTATION_ARRAY_INIT ANNOTATION_ARRAY_INIT}. 373 */ 374 private void logCommaViolation(final DetailAST aAST) 375 { 376 final DetailAST rCurly = aAST.findFirstToken(TokenTypes.RCURLY); 377 378 //comma can be null if array is empty 379 final DetailAST comma = rCurly.getPreviousSibling(); 380 381 if (TrailingArrayComma.ALWAYS.equals(this.mComma) 382 && (comma == null || comma.getType() != TokenTypes.COMMA)) 383 { 384 this.log(rCurly.getLineNo(), 385 rCurly.getColumnNo(), "annotation.trailing.comma.missing"); 386 } 387 else if (TrailingArrayComma.NEVER.equals(this.mComma) 388 && comma != null && comma.getType() == TokenTypes.COMMA) 389 { 390 this.log(comma.getLineNo(), 391 comma.getColumnNo(), "annotation.trailing.comma.present"); 392 } 393 } 394 395 /** 396 * Checks to see if the closing parenthesis are present if required or 397 * prohibited. 398 * 399 * @param aAST the annotation token 400 */ 401 private void checkCheckClosingParens(final DetailAST aAST) 402 { 403 if (ClosingParens.IGNORE.equals(this.mParens) 404 || this.mParens == null) 405 { 406 return; 407 } 408 409 final DetailAST paren = aAST.getLastChild(); 410 final boolean parenExists = paren.getType() == TokenTypes.RPAREN; 411 412 if (ClosingParens.ALWAYS.equals(this.mParens) 413 && !parenExists) 414 { 415 this.log(aAST.getLineNo(), "annotation.parens.missing"); 416 } 417 else if (ClosingParens.NEVER.equals(this.mParens) 418 && !aAST.branchContains(TokenTypes.EXPR) 419 && parenExists) 420 { 421 this.log(aAST.getLineNo(), "annotation.parens.present"); 422 } 423 } 424 425 /** 426 * Defines the styles for defining elements in an annotation. 427 * @author Travis Schneeberger 428 */ 429 public static enum ElementStyle { 430 431 /** 432 * expanded example 433 * 434 * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. 435 */ 436 EXPANDED, 437 438 /** 439 * compact example 440 * 441 * <pre>@SuppressWarnings({"unchecked","unused",})</pre> 442 * <br>or<br> 443 * <pre>@SuppressWarnings("unchecked")</pre>. 444 */ 445 COMPACT, 446 447 /** 448 * compact example.] 449 * 450 * <pre>@SuppressWarnings("unchecked")</pre>. 451 */ 452 COMPACT_NO_ARRAY, 453 454 /** 455 * mixed styles. 456 */ 457 IGNORE, 458 } 459 460 /** 461 * Defines the two styles for defining 462 * elements in an annotation. 463 * 464 * @author Travis Schneeberger 465 */ 466 public static enum TrailingArrayComma { 467 468 /** 469 * with comma example 470 * 471 * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. 472 */ 473 ALWAYS, 474 475 /** 476 * without comma example 477 * 478 * <pre>@SuppressWarnings(value={"unchecked","unused"})</pre>. 479 */ 480 NEVER, 481 482 /** 483 * mixed styles. 484 */ 485 IGNORE, 486 } 487 488 /** 489 * Defines the two styles for defining 490 * elements in an annotation. 491 * 492 * @author Travis Schneeberger 493 */ 494 public static enum ClosingParens { 495 496 /** 497 * with parens example 498 * 499 * <pre>@Deprecated()</pre>. 500 */ 501 ALWAYS, 502 503 /** 504 * without parens example 505 * 506 * <pre>@Deprecated</pre>. 507 */ 508 NEVER, 509 510 /** 511 * mixed styles. 512 */ 513 IGNORE, 514 } 515}