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