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.metrics; 021 022import com.puppycrawl.tools.checkstyle.api.TokenTypes; 023 024/** 025 * <p> 026 * Checks the number of other types a given class/record/interface/enum/annotation 027 * relies on. Also, the square of this has been shown to indicate the amount 028 * of maintenance required in functional programs (on a file basis) at least. 029 * </p> 030 * <p> 031 * This check processes files in the following way: 032 * </p> 033 * <ol> 034 * <li> 035 * Iterates over all tokens that might contain type reference. 036 * </li> 037 * <li> 038 * If a class was imported with direct import (i.e. {@code import java.math.BigDecimal}), 039 * or the class was referenced with the package name (i.e. {@code java.math.BigDecimal value}) 040 * and the package was added to the {@code excludedPackages} parameter, 041 * the class does not increase complexity. 042 * </li> 043 * <li> 044 * If a class name was added to the {@code excludedClasses} parameter, 045 * the class does not increase complexity. 046 * </li> 047 * </ol> 048 * <ul> 049 * <li> 050 * Property {@code max} - Specify the maximum threshold allowed. 051 * Type is {@code int}. 052 * Default value is {@code 20}. 053 * </li> 054 * <li> 055 * Property {@code excludedClasses} - Specify user-configured class names to ignore. 056 * Type is {@code java.lang.String[]}. 057 * Default value is {@code ArrayIndexOutOfBoundsException, ArrayList, Boolean, Byte, 058 * Character, Class, Collection, Deprecated, Deque, Double, DoubleStream, EnumSet, Exception, 059 * Float, FunctionalInterface, HashMap, HashSet, IllegalArgumentException, IllegalStateException, 060 * IndexOutOfBoundsException, IntStream, Integer, LinkedHashMap, LinkedHashSet, LinkedList, List, 061 * Long, LongStream, Map, NullPointerException, Object, Optional, OptionalDouble, OptionalInt, 062 * OptionalLong, Override, Queue, RuntimeException, SafeVarargs, SecurityException, Set, Short, 063 * SortedMap, SortedSet, Stream, String, StringBuffer, StringBuilder, SuppressWarnings, Throwable, 064 * TreeMap, TreeSet, UnsupportedOperationException, Void, boolean, byte, char, double, 065 * float, int, long, short, var, void}. 066 * </li> 067 * <li> 068 * Property {@code excludeClassesRegexps} - Specify user-configured regular 069 * expressions to ignore classes. 070 * Type is {@code java.util.regex.Pattern[]}. 071 * Default value is {@code ^$}. 072 * </li> 073 * <li> 074 * Property {@code excludedPackages} - Specify user-configured packages to ignore. 075 * All excluded packages should end with a period, so it also appends a dot to a package name. 076 * Type is {@code java.lang.String[]}. 077 * Default value is {@code ""}. 078 * </li> 079 * </ul> 080 * <p> 081 * To configure the check: 082 * </p> 083 * <pre> 084 * <module name="ClassFanOutComplexity"/> 085 * </pre> 086 * <p> 087 * Example: 088 * </p> 089 * <p> 090 * The check passes without violations in the following: 091 * </p> 092 * <pre> 093 * class InputClassComplexity { 094 * Set set = new HashSet(); // Set, HashSet ignored due to default excludedClasses property 095 * Map map = new HashMap(); // Map, HashMap ignored due to default excludedClasses property 096 * Date date = new Date(); // Counted, 1 097 * Time time = new Time(); // Counted, 2 098 * Place place = new Place(); // Counted, 3 099 * int value = 10; // int is ignored due to default excludedClasses property 100 * void method() { 101 * var result = "result"; // var is ignored due to default excludedClasses property 102 * } 103 * } 104 * </pre> 105 * <p> 106 * The check results in a violation in the following: 107 * </p> 108 * <pre> 109 * class InputClassComplexity { 110 * Set set = new HashSet(); // Set, HashSet ignored due to default excludedClasses property 111 * Map map = new HashMap(); // Map, HashMap ignored due to default excludedClasses property 112 * Date date = new Date(); // Counted, 1 113 * Time time = new Time(); // Counted, 2 114 * // mention of 18 other user defined classes 115 * Place place = new Place(); // violation, total is 21 116 * } 117 * </pre> 118 * <p> 119 * To configure the check with a threshold of 2: 120 * </p> 121 * <pre> 122 * <module name="ClassFanOutComplexity"> 123 * <property name="max" value="2"/> 124 * </module> 125 * </pre> 126 * <p> 127 * Example: 128 * </p> 129 * <p> 130 * The check passes without violations in the following: 131 * </p> 132 * <pre> 133 * class InputClassComplexity { 134 * Set set = new HashSet(); // Set, HashSet ignored due to default excludedClasses property 135 * Map map = new HashMap(); // Map, HashMap ignored due to default excludedClasses property 136 * Date date = new Date(); // Counted, 1 137 * Time time = new Time(); // Counted, 2 138 * } 139 * </pre> 140 * <p> 141 * The check results in a violation in the following: 142 * </p> 143 * <pre> 144 * class InputClassComplexity { 145 * Set set = new HashSet(); // Set, HashSet ignored due to default excludedClasses property 146 * Map map = new HashMap(); // Map, HashMap ignored due to default excludedClasses property 147 * Date date = new Date(); // Counted, 1 148 * Time time = new Time(); // Counted, 2 149 * Place place = new Place(); // violation, total is 3 150 * } 151 * </pre> 152 * <p> 153 * To configure the check with three excluded classes {@code HashMap}, 154 * {@code HashSet} and {@code Place}: 155 * </p> 156 * <pre> 157 * <module name="ClassFanOutComplexity"> 158 * <property name="excludedClasses" value="HashMap, HashSet, Place"/> 159 * </module> 160 * </pre> 161 * <p> 162 * Example: 163 * </p> 164 * <p> 165 * The check passes without violations in the following: 166 * </p> 167 * <pre> 168 * class InputClassComplexity { 169 * Set set = new HashSet(); // Set counted 1, HashSet ignored 170 * Map map = new HashMap(); // Map counted 2, HashMap ignored 171 * Date date = new Date(); // Counted, 3 172 * Time time = new Time(); // Counted, 4 173 * // mention of 16 other user defined classes 174 * Place place = new Place(); // Ignored 175 * } 176 * </pre> 177 * <p> 178 * The check results in a violation in the following: 179 * </p> 180 * <pre> 181 * class InputClassComplexity { 182 * Set set = new HashSet(); // Set counted 1, HashSet ignored 183 * Map map = new HashMap(); // Map counted 2, HashMap ignored 184 * Date date = new Date(); // Counted, 3 185 * Time time = new Time(); // Counted, 4 186 * // mention of 16 other user defined classes 187 * Space space = new Space(); // violation, total is 21 188 * } 189 * </pre> 190 * <p> 191 * To configure the check to exclude classes with a regular expression 192 * {@code .*Reader$}: 193 * </p> 194 * <pre> 195 * <module name="ClassFanOutComplexity"> 196 * <property name="excludeClassesRegexps" value=".*Reader$"/> 197 * </module> 198 * </pre> 199 * <p> 200 * Example: 201 * </p> 202 * <p> 203 * The check passes without violations in the following: 204 * </p> 205 * <pre> 206 * class InputClassComplexity { 207 * Set set = new HashSet(); // Set, HashSet ignored due to default excludedClasses property 208 * Map map = new HashMap(); // Map, HashMap ignored due to default excludedClasses property 209 * Date date = new Date(); // Counted, 1 210 * Time time = new Time(); // Counted, 2 211 * // mention of 18 other user defined classes 212 * BufferedReader br; // Ignored 213 * } 214 * </pre> 215 * <p> 216 * The check results in a violation in the following: 217 * </p> 218 * <pre> 219 * class InputClassComplexity { 220 * Set set = new HashSet(); // Set, HashSet ignored due to default excludedClasses property 221 * Map map = new HashMap(); // Map, HashMap ignored due to default excludedClasses property 222 * Date date = new Date(); // Counted, 1 223 * Time time = new Time(); // Counted, 2 224 * // mention of 18 other user defined classes 225 * File file; // violation, total is 21 226 * } 227 * </pre> 228 * <p> 229 * To configure the check with an excluded package {@code java.io}: 230 * </p> 231 * <pre> 232 * <module name="ClassFanOutComplexity"> 233 * <property name="excludedPackages" value="java.io"/> 234 * </module> 235 * </pre> 236 * <p> 237 * Example: 238 * </p> 239 * <p> 240 * The check passes without violations in the following: 241 * </p> 242 * <pre> 243 * import java.io.BufferedReader; 244 * 245 * class InputClassComplexity { 246 * Set set = new HashSet(); // Set, HashSet ignored due to default excludedClasses property 247 * Map map = new HashMap(); // Map, HashMap ignored due to default excludedClasses property 248 * Date date = new Date(); // Counted, 1 249 * Time time = new Time(); // Counted, 2 250 * // mention of 18 other user defined classes 251 * BufferedReader br; // Ignored 252 * } 253 * </pre> 254 * <p> 255 * The check results in a violation in the following: 256 * </p> 257 * <pre> 258 * import java.util.StringTokenizer; 259 * 260 * class InputClassComplexity { 261 * Set set = new HashSet(); // Set, HashSet ignored due to default excludedClasses property 262 * Map map = new HashMap(); // Map, HashMap ignored due to default excludedClasses property 263 * Date date = new Date(); // Counted, 1 264 * Time time = new Time(); // Counted, 2 265 * // mention of 18 other user defined classes 266 * StringTokenizer st; // violation, total is 21 267 * } 268 * </pre> 269 * <p> 270 * Override property {@code excludedPackages} to mark some packages as excluded. 271 * Each member of {@code excludedPackages} should be a valid identifier: 272 * </p> 273 * <ul> 274 * <li> 275 * {@code java.util} - valid, excludes all classes inside {@code java.util}, 276 * but not from the subpackages. 277 * </li> 278 * <li> 279 * {@code java.util.} - invalid, should not end with a dot. 280 * </li> 281 * <li> 282 * {@code java.util.*} - invalid, should not end with a star. 283 * </li> 284 * </ul> 285 * <p> 286 * Note, that checkstyle will ignore all classes from the {@code java.lang} 287 * package and its subpackages, even if the {@code java.lang} was not listed 288 * in the {@code excludedPackages} parameter. 289 * </p> 290 * <p> 291 * Also note, that {@code excludedPackages} will not exclude classes, imported 292 * via wildcard (e.g. {@code import java.math.*}). Instead of wildcard import 293 * you should use direct import (e.g. {@code import java.math.BigDecimal}). 294 * </p> 295 * <p> 296 * Also note, that checkstyle will not exclude classes within the same file even 297 * if it was listed in the {@code excludedPackages} parameter. 298 * For example, assuming the config is 299 * </p> 300 * <pre> 301 * <module name="ClassFanOutComplexity"> 302 * <property name="excludedPackages" value="a.b"/> 303 * </module> 304 * </pre> 305 * <p> 306 * And the file {@code a.b.Foo.java} is: 307 * </p> 308 * <pre> 309 * package a.b; 310 * 311 * import a.b.Bar; 312 * import a.b.c.Baz; 313 * 314 * class Foo { 315 * Bar bar; // Will be ignored, located inside ignored a.b package 316 * Baz baz; // Will not be ignored, located inside a.b.c package 317 * Data data; // Will not be ignored, same file 318 * 319 * class Data { 320 * Foo foo; // Will not be ignored, same file 321 * } 322 * } 323 * </pre> 324 * <p> 325 * The {@code bar} member will not be counted, since the {@code a.b} 326 * added to the {@code excludedPackages}. The {@code baz} member will be counted, 327 * since the {@code a.b.c} was not added to the {@code excludedPackages}. 328 * The {@code data} and {@code foo} members will be counted, as they are inside same file. 329 * </p> 330 * <p> 331 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 332 * </p> 333 * <p> 334 * Violation Message Keys: 335 * </p> 336 * <ul> 337 * <li> 338 * {@code classFanOutComplexity} 339 * </li> 340 * </ul> 341 * 342 * @since 3.4 343 */ 344public final class ClassFanOutComplexityCheck extends AbstractClassCouplingCheck { 345 346 /** 347 * A key is pointing to the warning message text in "messages.properties" 348 * file. 349 */ 350 public static final String MSG_KEY = "classFanOutComplexity"; 351 352 /** Default value of max value. */ 353 private static final int DEFAULT_MAX = 20; 354 355 /** Creates new instance of this check. */ 356 public ClassFanOutComplexityCheck() { 357 super(DEFAULT_MAX); 358 } 359 360 @Override 361 public int[] getRequiredTokens() { 362 return new int[] { 363 TokenTypes.PACKAGE_DEF, 364 TokenTypes.IMPORT, 365 TokenTypes.CLASS_DEF, 366 TokenTypes.EXTENDS_CLAUSE, 367 TokenTypes.IMPLEMENTS_CLAUSE, 368 TokenTypes.ANNOTATION, 369 TokenTypes.INTERFACE_DEF, 370 TokenTypes.ENUM_DEF, 371 TokenTypes.TYPE, 372 TokenTypes.LITERAL_NEW, 373 TokenTypes.LITERAL_THROWS, 374 TokenTypes.ANNOTATION_DEF, 375 TokenTypes.RECORD_DEF, 376 }; 377 } 378 379 @Override 380 public int[] getAcceptableTokens() { 381 return getRequiredTokens(); 382 } 383 384 @Override 385 protected String getLogMessageId() { 386 return MSG_KEY; 387 } 388 389}