1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2024 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.sizes;
21
22 import java.util.ArrayDeque;
23 import java.util.BitSet;
24 import java.util.Deque;
25 import java.util.Objects;
26 import java.util.stream.Stream;
27
28 import com.puppycrawl.tools.checkstyle.StatelessCheck;
29 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
30 import com.puppycrawl.tools.checkstyle.api.DetailAST;
31 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
33
34 /**
35 * <div>
36 * Checks for long methods and constructors.
37 * </div>
38 *
39 * <p>
40 * Rationale: If a method becomes very long it is hard to understand.
41 * Therefore, long methods should usually be refactored into several
42 * individual methods that focus on a specific task.
43 * </p>
44 * <ul>
45 * <li>
46 * Property {@code countEmpty} - Control whether to count empty lines and comments.
47 * Type is {@code boolean}.
48 * Default value is {@code true}.
49 * </li>
50 * <li>
51 * Property {@code max} - Specify the maximum number of lines allowed.
52 * Type is {@code int}.
53 * Default value is {@code 150}.
54 * </li>
55 * <li>
56 * Property {@code tokens} - tokens to check
57 * Type is {@code java.lang.String[]}.
58 * Validation type is {@code tokenSet}.
59 * Default value is:
60 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
61 * METHOD_DEF</a>,
62 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
63 * CTOR_DEF</a>,
64 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
65 * COMPACT_CTOR_DEF</a>.
66 * </li>
67 * </ul>
68 *
69 * <p>
70 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
71 * </p>
72 *
73 * <p>
74 * Violation Message Keys:
75 * </p>
76 * <ul>
77 * <li>
78 * {@code maxLen.method}
79 * </li>
80 * </ul>
81 *
82 * @since 3.0
83 */
84 @StatelessCheck
85 public class MethodLengthCheck extends AbstractCheck {
86
87 /**
88 * A key is pointing to the warning message text in "messages.properties"
89 * file.
90 */
91 public static final String MSG_KEY = "maxLen.method";
92
93 /** Default maximum number of lines. */
94 private static final int DEFAULT_MAX_LINES = 150;
95
96 /** Control whether to count empty lines and comments. */
97 private boolean countEmpty = true;
98
99 /** Specify the maximum number of lines allowed. */
100 private int max = DEFAULT_MAX_LINES;
101
102 @Override
103 public int[] getDefaultTokens() {
104 return getAcceptableTokens();
105 }
106
107 @Override
108 public int[] getAcceptableTokens() {
109 return new int[] {
110 TokenTypes.METHOD_DEF,
111 TokenTypes.CTOR_DEF,
112 TokenTypes.COMPACT_CTOR_DEF,
113 };
114 }
115
116 @Override
117 public int[] getRequiredTokens() {
118 return CommonUtil.EMPTY_INT_ARRAY;
119 }
120
121 @Override
122 public void visitToken(DetailAST ast) {
123 final DetailAST openingBrace = ast.findFirstToken(TokenTypes.SLIST);
124 if (openingBrace != null) {
125 final int length;
126 if (countEmpty) {
127 final DetailAST closingBrace = openingBrace.findFirstToken(TokenTypes.RCURLY);
128 length = getLengthOfBlock(openingBrace, closingBrace);
129 }
130 else {
131 length = countUsedLines(openingBrace);
132 }
133 if (length > max) {
134 final String methodName = ast.findFirstToken(TokenTypes.IDENT).getText();
135 log(ast, MSG_KEY, length, max, methodName);
136 }
137 }
138 }
139
140 /**
141 * Returns length of code.
142 *
143 * @param openingBrace block opening brace
144 * @param closingBrace block closing brace
145 * @return number of lines with code for current block
146 */
147 private static int getLengthOfBlock(DetailAST openingBrace, DetailAST closingBrace) {
148 final int startLineNo = openingBrace.getLineNo();
149 final int endLineNo = closingBrace.getLineNo();
150 return endLineNo - startLineNo + 1;
151 }
152
153 /**
154 * Count number of used code lines without comments.
155 *
156 * @param ast start ast
157 * @return number of used lines of code
158 */
159 private static int countUsedLines(DetailAST ast) {
160 final Deque<DetailAST> nodes = new ArrayDeque<>();
161 nodes.add(ast);
162 final BitSet usedLines = new BitSet();
163 while (!nodes.isEmpty()) {
164 final DetailAST node = nodes.removeFirst();
165 final int lineIndex = node.getLineNo();
166 // text block requires special treatment,
167 // since it is the only non-comment token that can span more than one line
168 if (node.getType() == TokenTypes.TEXT_BLOCK_LITERAL_BEGIN) {
169 final int endLineIndex = node.getLastChild().getLineNo();
170 usedLines.set(lineIndex, endLineIndex + 1);
171 }
172 else {
173 usedLines.set(lineIndex);
174 Stream.iterate(
175 node.getLastChild(), Objects::nonNull, DetailAST::getPreviousSibling
176 ).forEach(nodes::addFirst);
177 }
178 }
179 return usedLines.cardinality();
180 }
181
182 /**
183 * Setter to specify the maximum number of lines allowed.
184 *
185 * @param length the maximum length of a method.
186 * @since 3.0
187 */
188 public void setMax(int length) {
189 max = length;
190 }
191
192 /**
193 * Setter to control whether to count empty lines and comments.
194 *
195 * @param countEmpty whether to count empty and comments.
196 * @since 3.2
197 */
198 public void setCountEmpty(boolean countEmpty) {
199 this.countEmpty = countEmpty;
200 }
201
202 }