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 * &lt;module name="ClassFanOutComplexity"/&gt;
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 * &lt;module name="ClassFanOutComplexity"&gt;
123 *   &lt;property name="max" value="2"/&gt;
124 * &lt;/module&gt;
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 * &lt;module name="ClassFanOutComplexity"&gt;
158 *   &lt;property name="excludedClasses" value="HashMap, HashSet, Place"/&gt;
159 * &lt;/module&gt;
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 * &lt;module name="ClassFanOutComplexity"&gt;
196 *   &lt;property name="excludeClassesRegexps" value=".*Reader$"/&gt;
197 * &lt;/module&gt;
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 * &lt;module name="ClassFanOutComplexity"&gt;
233 *   &lt;property name="excludedPackages" value="java.io"/&gt;
234 * &lt;/module&gt;
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 * &lt;module name="ClassFanOutComplexity"&gt;
302 *   &lt;property name="excludedPackages" value="a.b"/&gt;
303 * &lt;/module&gt;
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}