Coverage Report - com.puppycrawl.tools.checkstyle.checks.design.ThrowsCountCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
ThrowsCountCheck
100%
40/40
100%
22/22
2
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 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  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.design;
 21  
 
 22  
 import com.puppycrawl.tools.checkstyle.StatelessCheck;
 23  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 24  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 25  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 26  
 
 27  
 /**
 28  
  * <p>
 29  
  * Restricts throws statements to a specified count (default = 4).
 30  
  * Methods with "Override" or "java.lang.Override" annotation are skipped
 31  
  * from validation as current class cannot change signature of these methods.
 32  
  * </p>
 33  
  * <p>
 34  
  * Rationale:
 35  
  * Exceptions form part of a methods interface. Declaring
 36  
  * a method to throw too many differently rooted
 37  
  * exceptions makes exception handling onerous and leads
 38  
  * to poor programming practices such as catch
 39  
  * (Exception). 4 is the empirical value which is based
 40  
  * on reports that we had for the ThrowsCountCheck over big projects
 41  
  * such as OpenJDK. This check also forces developers to put exceptions
 42  
  * into a hierarchy such that in the simplest
 43  
  * case, only one type of exception need be checked for by
 44  
  * a caller but allows any sub-classes to be caught
 45  
  * specifically if necessary. For more information on rules
 46  
  * for the exceptions and their issues, see Effective Java:
 47  
  * Programming Language Guide Second Edition
 48  
  * by Joshua Bloch pages 264-273.
 49  
  * </p>
 50  
  * <p>
 51  
  * <b>ignorePrivateMethods</b> - allows to skip private methods as they do
 52  
  * not cause problems for other classes.
 53  
  * </p>
 54  
  * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
 55  
  */
 56  
 @StatelessCheck
 57  
 public final class ThrowsCountCheck extends AbstractCheck {
 58  
 
 59  
     /**
 60  
      * A key is pointing to the warning message text in "messages.properties"
 61  
      * file.
 62  
      */
 63  
     public static final String MSG_KEY = "throws.count";
 64  
 
 65  
     /** Default value of max property. */
 66  
     private static final int DEFAULT_MAX = 4;
 67  
 
 68  
     /** Whether private methods must be ignored. **/
 69  15
     private boolean ignorePrivateMethods = true;
 70  
 
 71  
     /** Maximum allowed throws statements. */
 72  
     private int max;
 73  
 
 74  
     /** Creates new instance of the check. */
 75  15
     public ThrowsCountCheck() {
 76  15
         max = DEFAULT_MAX;
 77  15
     }
 78  
 
 79  
     @Override
 80  
     public int[] getDefaultTokens() {
 81  37
         return new int[] {
 82  
             TokenTypes.LITERAL_THROWS,
 83  
         };
 84  
     }
 85  
 
 86  
     @Override
 87  
     public int[] getRequiredTokens() {
 88  19
         return getDefaultTokens();
 89  
     }
 90  
 
 91  
     @Override
 92  
     public int[] getAcceptableTokens() {
 93  5
         return new int[] {
 94  
             TokenTypes.LITERAL_THROWS,
 95  
         };
 96  
     }
 97  
 
 98  
     /**
 99  
      * Sets whether private methods must be ignored.
 100  
      * @param ignorePrivateMethods whether private methods must be ignored.
 101  
      */
 102  
     public void setIgnorePrivateMethods(boolean ignorePrivateMethods) {
 103  2
         this.ignorePrivateMethods = ignorePrivateMethods;
 104  2
     }
 105  
 
 106  
     /**
 107  
      * Setter for max property.
 108  
      * @param max maximum allowed throws statements.
 109  
      */
 110  
     public void setMax(int max) {
 111  2
         this.max = max;
 112  2
     }
 113  
 
 114  
     @Override
 115  
     public void visitToken(DetailAST ast) {
 116  33
         if (ast.getType() == TokenTypes.LITERAL_THROWS) {
 117  32
             visitLiteralThrows(ast);
 118  
         }
 119  
         else {
 120  1
             throw new IllegalStateException(ast.toString());
 121  
         }
 122  32
     }
 123  
 
 124  
     /**
 125  
      * Checks number of throws statements.
 126  
      * @param ast throws for check.
 127  
      */
 128  
     private void visitLiteralThrows(DetailAST ast) {
 129  32
         if ((!ignorePrivateMethods || !isInPrivateMethod(ast))
 130  30
                 && !isOverriding(ast)) {
 131  
             // Account for all the commas!
 132  20
             final int count = (ast.getChildCount() + 1) / 2;
 133  20
             if (count > max) {
 134  22
                 log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY,
 135  11
                     count, max);
 136  
             }
 137  
         }
 138  32
     }
 139  
 
 140  
     /**
 141  
      * Check if a method has annotation @Override.
 142  
      * @param ast throws, which is being checked.
 143  
      * @return true, if a method has annotation @Override.
 144  
      */
 145  
     private static boolean isOverriding(DetailAST ast) {
 146  30
         final DetailAST modifiers = ast.getParent().findFirstToken(TokenTypes.MODIFIERS);
 147  30
         boolean isOverriding = false;
 148  30
         if (modifiers.findFirstToken(TokenTypes.ANNOTATION) != null) {
 149  13
             DetailAST child = modifiers.getFirstChild();
 150  22
             while (child != null) {
 151  19
                 if (child.getType() == TokenTypes.ANNOTATION
 152  16
                         && "Override".equals(getAnnotationName(child))) {
 153  10
                     isOverriding = true;
 154  10
                     break;
 155  
                 }
 156  9
                 child = child.getNextSibling();
 157  
             }
 158  
         }
 159  30
         return isOverriding;
 160  
     }
 161  
 
 162  
     /**
 163  
      * Gets name of an annotation.
 164  
      * @param annotation to get name of.
 165  
      * @return name of an annotation.
 166  
      */
 167  
     private static String getAnnotationName(DetailAST annotation) {
 168  16
         final DetailAST dotAst = annotation.findFirstToken(TokenTypes.DOT);
 169  
         final String name;
 170  16
         if (dotAst == null) {
 171  13
             name = annotation.findFirstToken(TokenTypes.IDENT).getText();
 172  
         }
 173  
         else {
 174  3
             name = dotAst.findFirstToken(TokenTypes.IDENT).getText();
 175  
         }
 176  16
         return name;
 177  
     }
 178  
 
 179  
     /**
 180  
      * Checks if method, which throws an exception is private.
 181  
      * @param ast throws, which is being checked.
 182  
      * @return true, if method, which throws an exception is private.
 183  
      */
 184  
     private static boolean isInPrivateMethod(DetailAST ast) {
 185  22
         final DetailAST methodModifiers = ast.getParent().findFirstToken(TokenTypes.MODIFIERS);
 186  22
         return methodModifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
 187  
     }
 188  
 }