Coverage Report - com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
AnnotationUseStyleCheck
96%
76/79
92%
65/70
3.667
AnnotationUseStyleCheck$ClosingParens
100%
4/4
N/A
3.667
AnnotationUseStyleCheck$ElementStyle
100%
5/5
N/A
3.667
AnnotationUseStyleCheck$TrailingArrayComma
100%
4/4
N/A
3.667
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  Oliver Burn
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 package com.puppycrawl.tools.checkstyle.checks.annotation;
 20  
 
 21  
 import org.apache.commons.beanutils.ConversionException;
 22  
 
 23  
 import com.puppycrawl.tools.checkstyle.api.Check;
 24  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 25  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 26  
 
 27  
 /**
 28  
  * This check controls the style with the usage of annotations.
 29  
  *
 30  
  * <p>
 31  
  * Annotations have three element styles starting with the least verbose.
 32  
  * <ul>
 33  
  * <li>{@link ElementStyle#COMPACT_NO_ARRAY COMPACT_NO_ARRAY}</li>
 34  
  * <li>{@link ElementStyle#COMPACT COMPACT}</li>
 35  
  * <li>{@link ElementStyle#EXPANDED EXPANDED}</li>
 36  
  * </ul>
 37  
  * To not enforce an element style
 38  
  * a {@link ElementStyle#IGNORE IGNORE} type is provided.  The desired style
 39  
  * can be set through the <code>elementStyle</code> property.
 40  
  * </p>
 41  
  *
 42  
  * <p>
 43  
  * Using the EXPANDED style is more verbose. The expanded version
 44  
  * is sometimes referred to as "named parameters" in other languages.
 45  
  * </p>
 46  
  *
 47  
  * <p>
 48  
  * Using the COMPACT style is less verbose. This style can only
 49  
  * be used when there is an element called 'value' which is  either
 50  
  * the sole element or all other elements have default valuess.
 51  
  * </p>
 52  
  *
 53  
  * <p>
 54  
  * Using the COMPACT_NO_ARRAY style is less verbose. It is similar
 55  
  * to the COMPACT style but single value arrays are flagged. With
 56  
  * annotations a single value array does not need to be placed in an
 57  
  * array initializer. This style can only be used when there is an
 58  
  * element called 'value' which is either the sole element or all other
 59  
  * elements have default values.
 60  
  * </p>
 61  
  *
 62  
  * <p>
 63  
  * The ending parenthesis are optional when using annotations with no elements.
 64  
  * To always require ending parenthesis use the
 65  
  * {@link ClosingParens#ALWAYS ALWAYS} type.  To never have ending parenthesis
 66  
  * use the {@link ClosingParens#NEVER NEVER} type. To not enforce a
 67  
  * closing parenthesis preference a {@link ClosingParens#IGNORE IGNORE} type is
 68  
  * provided. Set this through the <code>closingParens</code> property.
 69  
  * </p>
 70  
  *
 71  
  * <p>
 72  
  * Annotations also allow you to specify arrays of elements in a standard
 73  
  * format.  As with normal arrays, a trailing comma is optional. To always
 74  
  * require a trailing comma use the {@link TrailingArrayComma#ALWAYS ALWAYS}
 75  
  * type. To never have a trailing  comma use the
 76  
  * {@link TrailingArrayComma#NEVER NEVER} type. To not enforce a trailing
 77  
  * array comma preference a {@link TrailingArrayComma#IGNORE IGNORE} type
 78  
  * is provided.  Set this through the <code>trailingArrayComma</code> property.
 79  
  * </p>
 80  
  *
 81  
  * <p>
 82  
  * By default the ElementStyle is set to EXPANDED, the TrailingArrayComma
 83  
  * is set to NEVER, and the ClosingParans is set to ALWAYS.
 84  
  * </p>
 85  
  *
 86  
  * <p>
 87  
  * According to the JLS, it is legal to include a trailing comma
 88  
  * in arrays used in annotations but Sun's Java 5 & 6 compilers will not
 89  
  * compile with this syntax. This may in be a bug in Sun's compilers
 90  
  * since eclipse 3.4's built-in compiler does allow this syntax as
 91  
  * defined in the JLS. Note: this was tested with compilers included with
 92  
  * JDK versions 1.5.0.17 and 1.6.0.11 and the compiler included with eclipse
 93  
  * 3.4.1.
 94  
  *
 95  
  * See <a
 96  
  * href="http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html">
 97  
  * Java Language specification, sections 9.7</a>.
 98  
  * </p>
 99  
  *
 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  
  * </p>
 105  
  *
 106  
  * <pre>
 107  
  * &lt;module name=&quot;AnnotationUseStyle&quot;&gt;
 108  
  *    &lt;property name=&quot;ElementStyle&quot;
 109  
  *        value=&quot;EXPANDED&quot;/&gt;
 110  
  *    &lt;property name=&quot;TrailingArrayComma&quot;
 111  
  *        value=&quot;NEVER&quot;/&gt;
 112  
  *    &lt;property name=&quot;ClosingParens&quot;
 113  
  *        value=&quot;ALWAYS&quot;/&gt;
 114  
  * &lt;/module&gt;
 115  
  * </pre>
 116  
  *
 117  
  * @author Travis Schneeberger
 118  
  */
 119  10
 public 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  10
     private ElementStyle mStyle = ElementStyle.COMPACT_NO_ARRAY;
 133  
 
 134  
     //defaulting to NEVER because of the strange compiler behavior
 135  
     /** @see #setTrailingArrayComma(String) */
 136  10
     private TrailingArrayComma mComma = TrailingArrayComma.NEVER;
 137  
 
 138  
     /** @see #setClosingParans(String) */
 139  10
     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  10
         this.mStyle = this.getOption(ElementStyle.class, aStyle);
 150  10
     }
 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  10
         this.mComma = this.getOption(TrailingArrayComma.class, aComma);
 161  10
     }
 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  10
         this.mParens = this.getOption(ClosingParens.class, aParens);
 172  10
     }
 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  30
             return Enum.valueOf(aEnumClass, aString.trim().toUpperCase());
 186  
         }
 187  0
         catch (final IllegalArgumentException iae) {
 188  0
             throw new ConversionException("unable to parse " + aString, iae);
 189  
         }
 190  
     }
 191  
 
 192  
     /** {@inheritDoc} */
 193  
     @Override
 194  
     public int[] getDefaultTokens()
 195  
     {
 196  10
         return this.getRequiredTokens();
 197  
     }
 198  
 
 199  
     /** {@inheritDoc} */
 200  
     @Override
 201  
     public int[] getRequiredTokens()
 202  
     {
 203  10
         return new int[] {
 204  
             TokenTypes.ANNOTATION,
 205  
         };
 206  
     }
 207  
 
 208  
     /** {@inheritDoc} */
 209  
     @Override
 210  
     public int[] getAcceptableTokens()
 211  
     {
 212  0
         return this.getRequiredTokens();
 213  
     }
 214  
 
 215  
     /** {@inheritDoc} */
 216  
     @Override
 217  
     public void visitToken(final DetailAST aAST)
 218  
     {
 219  150
         this.checkStyleType(aAST);
 220  150
         this.checkCheckClosingParens(aAST);
 221  150
         this.checkTrailingComma(aAST);
 222  150
     }
 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  150
         if (ElementStyle.IGNORE.equals(this.mStyle)
 234  
             || this.mStyle == null)
 235  
         {
 236  90
             return;
 237  
         }
 238  
 
 239  60
         if (ElementStyle.COMPACT_NO_ARRAY.equals(this.mStyle)) {
 240  20
             this.checkCompactNoArrayStyle(aAnnotation);
 241  
         }
 242  40
         else if (ElementStyle.COMPACT.equals(this.mStyle)) {
 243  20
             this.checkCompactStyle(aAnnotation);
 244  
         }
 245  20
         else if (ElementStyle.EXPANDED.equals(this.mStyle)) {
 246  20
             this.checkExpandedStyle(aAnnotation);
 247  
         }
 248  60
     }
 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  20
         final int valuePairCount =
 258  
             aAnnotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
 259  
 
 260  20
         if (valuePairCount == 0
 261  
             && aAnnotation.branchContains(TokenTypes.EXPR))
 262  
         {
 263  7
             this.log(aAnnotation.getLineNo(), "annotation.incorrect.style",
 264  
                 ElementStyle.EXPANDED);
 265  
         }
 266  20
     }
 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  20
         final int valuePairCount =
 276  
             aAnnotation.getChildCount(
 277  
                 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
 278  
 
 279  20
         final DetailAST valuePair =
 280  
             aAnnotation.findFirstToken(
 281  
                 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
 282  
 
 283  20
         if (valuePairCount == 1
 284  
             && AnnotationUseStyleCheck.ANNOTATION_ELEMENT_SINGLE_NAME.equals(
 285  
                 valuePair.getFirstChild().getText()))
 286  
         {
 287  2
             this.log(aAnnotation.getLineNo(), "annotation.incorrect.style",
 288  
                 ElementStyle.COMPACT);
 289  
         }
 290  20
     }
 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  20
         final DetailAST arrayInit =
 300  
             aAnnotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT);
 301  
 
 302  20
         final int valuePairCount =
 303  
             aAnnotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
 304  
 
 305  20
         final DetailAST valuePair =
 306  
             aAnnotation.findFirstToken(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
 307  
 
 308  
         //in compact style with one value
 309  20
         if (arrayInit != null
 310  
             && arrayInit.getChildCount(TokenTypes.EXPR) == 1)
 311  
         {
 312  3
             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  17
         else if (valuePairCount == 1) {
 317  5
             final DetailAST nestedArrayInit =
 318  
                 valuePair.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT);
 319  
 
 320  5
             if (nestedArrayInit != null
 321  
                 && AnnotationUseStyleCheck.
 322  
                     ANNOTATION_ELEMENT_SINGLE_NAME.equals(
 323  
                     valuePair.getFirstChild().getText())
 324  
                     && nestedArrayInit.getChildCount(TokenTypes.EXPR) == 1)
 325  
             {
 326  2
                 this.log(aAnnotation.getLineNo(), "annotation.incorrect.style",
 327  
                     ElementStyle.COMPACT_NO_ARRAY);
 328  
             }
 329  
         }
 330  20
     }
 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  150
         if (TrailingArrayComma.IGNORE.equals(this.mComma)
 341  
             || this.mComma == null)
 342  
         {
 343  120
             return;
 344  
         }
 345  
 
 346  30
         DetailAST child = aAnnotation.getFirstChild();
 347  
 
 348  204
         while (child != null) {
 349  174
             DetailAST arrayInit = null;
 350  
 
 351  174
             if (child.getType()
 352  
                 == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR)
 353  
             {
 354  32
                 arrayInit =
 355  
                     child.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT);
 356  
             }
 357  142
             else if (child.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) {
 358  10
                 arrayInit = child;
 359  
             }
 360  
 
 361  174
             if (arrayInit != null) {
 362  42
                 this.logCommaViolation(arrayInit);
 363  
             }
 364  174
             child = child.getNextSibling();
 365  174
         }
 366  30
     }
 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  42
         final DetailAST rCurly = aAST.findFirstToken(TokenTypes.RCURLY);
 377  
 
 378  
         //comma can be null if array is empty
 379  42
         final DetailAST comma = rCurly.getPreviousSibling();
 380  
 
 381  42
         if (TrailingArrayComma.ALWAYS.equals(this.mComma)
 382  
             && (comma == null || comma.getType() != TokenTypes.COMMA))
 383  
         {
 384  13
             this.log(rCurly.getLineNo(),
 385  
                 rCurly.getColumnNo(), "annotation.trailing.comma.missing");
 386  
         }
 387  29
         else if (TrailingArrayComma.NEVER.equals(this.mComma)
 388  
             && comma != null && comma.getType() == TokenTypes.COMMA)
 389  
         {
 390  8
             this.log(comma.getLineNo(),
 391  
                 comma.getColumnNo(), "annotation.trailing.comma.present");
 392  
         }
 393  42
     }
 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  150
         if (ClosingParens.IGNORE.equals(this.mParens)
 404  
             || this.mParens == null)
 405  
         {
 406  110
             return;
 407  
         }
 408  
 
 409  40
         final DetailAST paren = aAST.getLastChild();
 410  40
         final boolean parenExists = paren.getType() == TokenTypes.RPAREN;
 411  
 
 412  40
         if (ClosingParens.ALWAYS.equals(this.mParens)
 413  
             && !parenExists)
 414  
         {
 415  3
             this.log(aAST.getLineNo(), "annotation.parens.missing");
 416  
         }
 417  37
         else if (ClosingParens.NEVER.equals(this.mParens)
 418  
             && !aAST.branchContains(TokenTypes.EXPR)
 419  
             && parenExists)
 420  
         {
 421  3
             this.log(aAST.getLineNo(), "annotation.parens.present");
 422  
         }
 423  40
     }
 424  
 
 425  
     /**
 426  
      * Defines the styles for defining elements in an annotation.
 427  
      * @author Travis Schneeberger
 428  
      */
 429  6
     public static enum ElementStyle {
 430  
 
 431  
         /**
 432  
          * expanded example
 433  
          *
 434  
          * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>.
 435  
          */
 436  1
         EXPANDED,
 437  
 
 438  
         /**
 439  
          * compact example
 440  
          *
 441  
          * <pre>@SuppressWarnings({"unchecked","unused",})</pre>
 442  
          * <br/>or<br/>
 443  
          * <pre>@SuppressWarnings("unchecked")</pre>.
 444  
          */
 445  1
         COMPACT,
 446  
 
 447  
         /**
 448  
          * compact example.]
 449  
          *
 450  
          * <pre>@SuppressWarnings("unchecked")</pre>.
 451  
          */
 452  1
         COMPACT_NO_ARRAY,
 453  
 
 454  
         /**
 455  
          * mixed styles.
 456  
          */
 457  1
         IGNORE,
 458  
     }
 459  
 
 460  
     /**
 461  
      * Defines the two styles for defining
 462  
      * elements in an annotation.
 463  
      *
 464  
      * @author Travis Schneeberger
 465  
      */
 466  5
     public static enum TrailingArrayComma {
 467  
 
 468  
         /**
 469  
          * with comma example
 470  
          *
 471  
          * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>.
 472  
          */
 473  1
         ALWAYS,
 474  
 
 475  
         /**
 476  
          * without comma example
 477  
          *
 478  
          * <pre>@SuppressWarnings(value={"unchecked","unused"})</pre>.
 479  
          */
 480  1
         NEVER,
 481  
 
 482  
         /**
 483  
          * mixed styles.
 484  
          */
 485  1
         IGNORE,
 486  
     }
 487  
 
 488  
     /**
 489  
      * Defines the two styles for defining
 490  
      * elements in an annotation.
 491  
      *
 492  
      * @author Travis Schneeberger
 493  
      */
 494  5
     public static enum ClosingParens {
 495  
 
 496  
         /**
 497  
          * with parens example
 498  
          *
 499  
          * <pre>@Deprecated()</pre>.
 500  
          */
 501  1
         ALWAYS,
 502  
 
 503  
         /**
 504  
          * without parens example
 505  
          *
 506  
          * <pre>@Deprecated</pre>.
 507  
          */
 508  1
         NEVER,
 509  
 
 510  
         /**
 511  
          * mixed styles.
 512  
          */
 513  1
         IGNORE,
 514  
     }
 515  
 }