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