001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.design; 021 022import com.puppycrawl.tools.checkstyle.StatelessCheck; 023import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026 027/** 028 * <p> 029 * Restricts throws statements to a specified count. 030 * Methods with "Override" or "java.lang.Override" annotation are skipped 031 * from validation as current class cannot change signature of these methods. 032 * </p> 033 * <p> 034 * Rationale: 035 * Exceptions form part of a method's interface. Declaring 036 * a method to throw too many differently rooted 037 * exceptions makes exception handling onerous and leads 038 * to poor programming practices such as writing code like 039 * {@code catch(Exception ex)}. 4 is the empirical value which is based 040 * on reports that we had for the ThrowsCountCheck over big projects 041 * such as OpenJDK. This check also forces developers to put exceptions 042 * into a hierarchy such that in the simplest 043 * case, only one type of exception need be checked for by 044 * a caller but any subclasses can be caught 045 * specifically if necessary. For more information on rules 046 * for the exceptions and their issues, see Effective Java: 047 * Programming Language Guide Second Edition 048 * by Joshua Bloch pages 264-273. 049 * </p> 050 * <p> 051 * <b>ignorePrivateMethods</b> - allows to skip private methods as they do 052 * not cause problems for other classes. 053 * </p> 054 * <ul> 055 * <li> 056 * Property {@code max} - Specify maximum allowed number of throws statements. 057 * Type is {@code int}. 058 * Default value is {@code 4}. 059 * </li> 060 * <li> 061 * Property {@code ignorePrivateMethods} - Allow private methods to be ignored. 062 * Type is {@code boolean}. 063 * Default value is {@code true}. 064 * </li> 065 * </ul> 066 * <p> 067 * To configure check: 068 * </p> 069 * <pre> 070 * <module name="ThrowsCount"/> 071 * </pre> 072 * <p> 073 * Example: 074 * </p> 075 * <pre> 076 * class Test { 077 * public void myFunction() throws CloneNotSupportedException, 078 * ArrayIndexOutOfBoundsException, 079 * StringIndexOutOfBoundsException, 080 * IllegalStateException, 081 * NullPointerException { // violation, max allowed is 4 082 * // body 083 * } 084 * 085 * public void myFunc() throws ArithmeticException, 086 * NumberFormatException { // ok 087 * // body 088 * } 089 * 090 * private void privateFunc() throws CloneNotSupportedException, 091 * ClassNotFoundException, 092 * IllegalAccessException, 093 * ArithmeticException, 094 * ClassCastException { // ok, private methods are ignored 095 * // body 096 * } 097 * 098 * } 099 * </pre> 100 * <p> 101 * To configure the check so that it doesn't allow more than two throws per method: 102 * </p> 103 * <pre> 104 * <module name="ThrowsCount"> 105 * <property name="max" value="2"/> 106 * </module> 107 * </pre> 108 * <p> 109 * Example: 110 * </p> 111 * <pre> 112 * class Test { 113 * public void myFunction() throws IllegalStateException, 114 * ArrayIndexOutOfBoundsException, 115 * NullPointerException { // violation, max allowed is 2 116 * // body 117 * } 118 * 119 * public void myFunc() throws ArithmeticException, 120 * NumberFormatException { // ok 121 * // body 122 * } 123 * 124 * private void privateFunc() throws CloneNotSupportedException, 125 * ClassNotFoundException, 126 * IllegalAccessException, 127 * ArithmeticException, 128 * ClassCastException { // ok, private methods are ignored 129 * // body 130 * } 131 * 132 * } 133 * </pre> 134 * <p> 135 * To configure the check so that it doesn't skip private methods: 136 * </p> 137 * <pre> 138 * <module name="ThrowsCount"> 139 * <property name="ignorePrivateMethods" value="false"/> 140 * </module> 141 * </pre> 142 * <p> 143 * Example: 144 * </p> 145 * <pre> 146 * class Test { 147 * public void myFunction() throws CloneNotSupportedException, 148 * ArrayIndexOutOfBoundsException, 149 * StringIndexOutOfBoundsException, 150 * IllegalStateException, 151 * NullPointerException { // violation, max allowed is 4 152 * // body 153 * } 154 * 155 * public void myFunc() throws ArithmeticException, 156 * NumberFormatException { // ok 157 * // body 158 * } 159 * 160 * private void privateFunc() throws CloneNotSupportedException, 161 * ClassNotFoundException, 162 * IllegalAccessException, 163 * ArithmeticException, 164 * ClassCastException { // violation, max allowed is 4 165 * // body 166 * } 167 * 168 * private void func() throws IllegalStateException, 169 * NullPointerException { // ok 170 * // body 171 * } 172 * 173 * } 174 * </pre> 175 * <p> 176 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 177 * </p> 178 * <p> 179 * Violation Message Keys: 180 * </p> 181 * <ul> 182 * <li> 183 * {@code throws.count} 184 * </li> 185 * </ul> 186 * 187 * @since 3.2 188 */ 189@StatelessCheck 190public final class ThrowsCountCheck extends AbstractCheck { 191 192 /** 193 * A key is pointing to the warning message text in "messages.properties" 194 * file. 195 */ 196 public static final String MSG_KEY = "throws.count"; 197 198 /** Default value of max property. */ 199 private static final int DEFAULT_MAX = 4; 200 201 /** Allow private methods to be ignored. */ 202 private boolean ignorePrivateMethods = true; 203 204 /** Specify maximum allowed number of throws statements. */ 205 private int max; 206 207 /** Creates new instance of the check. */ 208 public ThrowsCountCheck() { 209 max = DEFAULT_MAX; 210 } 211 212 @Override 213 public int[] getDefaultTokens() { 214 return getRequiredTokens(); 215 } 216 217 @Override 218 public int[] getRequiredTokens() { 219 return new int[] { 220 TokenTypes.LITERAL_THROWS, 221 }; 222 } 223 224 @Override 225 public int[] getAcceptableTokens() { 226 return getRequiredTokens(); 227 } 228 229 /** 230 * Setter to allow private methods to be ignored. 231 * 232 * @param ignorePrivateMethods whether private methods must be ignored. 233 */ 234 public void setIgnorePrivateMethods(boolean ignorePrivateMethods) { 235 this.ignorePrivateMethods = ignorePrivateMethods; 236 } 237 238 /** 239 * Setter to specify maximum allowed number of throws statements. 240 * 241 * @param max maximum allowed throws statements. 242 */ 243 public void setMax(int max) { 244 this.max = max; 245 } 246 247 @Override 248 public void visitToken(DetailAST ast) { 249 if (ast.getType() == TokenTypes.LITERAL_THROWS) { 250 visitLiteralThrows(ast); 251 } 252 else { 253 throw new IllegalStateException(ast.toString()); 254 } 255 } 256 257 /** 258 * Checks number of throws statements. 259 * 260 * @param ast throws for check. 261 */ 262 private void visitLiteralThrows(DetailAST ast) { 263 if ((!ignorePrivateMethods || !isInPrivateMethod(ast)) 264 && !isOverriding(ast)) { 265 // Account for all the commas! 266 final int count = (ast.getChildCount() + 1) / 2; 267 if (count > max) { 268 log(ast, MSG_KEY, count, max); 269 } 270 } 271 } 272 273 /** 274 * Check if a method has annotation @Override. 275 * 276 * @param ast throws, which is being checked. 277 * @return true, if a method has annotation @Override. 278 */ 279 private static boolean isOverriding(DetailAST ast) { 280 final DetailAST modifiers = ast.getParent().findFirstToken(TokenTypes.MODIFIERS); 281 boolean isOverriding = false; 282 DetailAST child = modifiers.getFirstChild(); 283 while (child != null) { 284 if (child.getType() == TokenTypes.ANNOTATION 285 && "Override".equals(getAnnotationName(child))) { 286 isOverriding = true; 287 break; 288 } 289 child = child.getNextSibling(); 290 } 291 return isOverriding; 292 } 293 294 /** 295 * Gets name of an annotation. 296 * 297 * @param annotation to get name of. 298 * @return name of an annotation. 299 */ 300 private static String getAnnotationName(DetailAST annotation) { 301 final DetailAST dotAst = annotation.findFirstToken(TokenTypes.DOT); 302 final String name; 303 if (dotAst == null) { 304 name = annotation.findFirstToken(TokenTypes.IDENT).getText(); 305 } 306 else { 307 name = dotAst.findFirstToken(TokenTypes.IDENT).getText(); 308 } 309 return name; 310 } 311 312 /** 313 * Checks if method, which throws an exception is private. 314 * 315 * @param ast throws, which is being checked. 316 * @return true, if method, which throws an exception is private. 317 */ 318 private static boolean isInPrivateMethod(DetailAST ast) { 319 final DetailAST methodModifiers = ast.getParent().findFirstToken(TokenTypes.MODIFIERS); 320 return methodModifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null; 321 } 322 323}