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