1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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 *
45 * @since 3.0
46 */
47 @StatelessCheck
48 public class MethodLengthCheck extends AbstractCheck {
49
50 /**
51 * A key is pointing to the warning message text in "messages.properties"
52 * file.
53 */
54 public static final String MSG_KEY = "maxLen.method";
55
56 /** Default maximum number of lines. */
57 private static final int DEFAULT_MAX_LINES = 150;
58
59 /** Control whether to count empty lines and comments. */
60 private boolean countEmpty = true;
61
62 /** Specify the maximum number of lines allowed. */
63 private int max = DEFAULT_MAX_LINES;
64
65 /**
66 * Creates a new {@code MethodLengthCheck} instance.
67 */
68 public MethodLengthCheck() {
69 // no code by default
70 }
71
72 @Override
73 public int[] getDefaultTokens() {
74 return getAcceptableTokens();
75 }
76
77 @Override
78 public int[] getAcceptableTokens() {
79 return new int[] {
80 TokenTypes.METHOD_DEF,
81 TokenTypes.CTOR_DEF,
82 TokenTypes.COMPACT_CTOR_DEF,
83 };
84 }
85
86 @Override
87 public int[] getRequiredTokens() {
88 return CommonUtil.EMPTY_INT_ARRAY;
89 }
90
91 @Override
92 public void visitToken(DetailAST ast) {
93 final DetailAST openingBrace = ast.findFirstToken(TokenTypes.SLIST);
94 if (openingBrace != null) {
95 final int length;
96 if (countEmpty) {
97 final DetailAST closingBrace = openingBrace.findFirstToken(TokenTypes.RCURLY);
98 length = getLengthOfBlock(openingBrace, closingBrace);
99 }
100 else {
101 length = countUsedLines(openingBrace);
102 }
103 if (length > max) {
104 final String methodName = ast.findFirstToken(TokenTypes.IDENT).getText();
105 log(ast, MSG_KEY, length, max, methodName);
106 }
107 }
108 }
109
110 /**
111 * Returns length of code.
112 *
113 * @param openingBrace block opening brace
114 * @param closingBrace block closing brace
115 * @return number of lines with code for current block
116 */
117 private static int getLengthOfBlock(DetailAST openingBrace, DetailAST closingBrace) {
118 final int startLineNo = openingBrace.getLineNo();
119 final int endLineNo = closingBrace.getLineNo();
120 return endLineNo - startLineNo + 1;
121 }
122
123 /**
124 * Count number of used code lines without comments.
125 *
126 * @param ast start ast
127 * @return number of used lines of code
128 */
129 private static int countUsedLines(DetailAST ast) {
130 final Deque<DetailAST> nodes = new ArrayDeque<>();
131 nodes.add(ast);
132 final BitSet usedLines = new BitSet();
133 while (!nodes.isEmpty()) {
134 final DetailAST node = nodes.removeFirst();
135 final int lineIndex = node.getLineNo();
136 // text block requires special treatment,
137 // since it is the only non-comment token that can span more than one line
138 if (node.getType() == TokenTypes.TEXT_BLOCK_LITERAL_BEGIN) {
139 final int endLineIndex = node.getLastChild().getLineNo();
140 usedLines.set(lineIndex, endLineIndex + 1);
141 }
142 else {
143 usedLines.set(lineIndex);
144 Stream.iterate(
145 node.getLastChild(), Objects::nonNull, DetailAST::getPreviousSibling
146 ).forEach(nodes::addFirst);
147 }
148 }
149 return usedLines.cardinality();
150 }
151
152 /**
153 * Setter to specify the maximum number of lines allowed.
154 *
155 * @param length the maximum length of a method.
156 * @since 3.0
157 */
158 public void setMax(int length) {
159 max = length;
160 }
161
162 /**
163 * Setter to control whether to count empty lines and comments.
164 *
165 * @param countEmpty whether to count empty and comments.
166 * @since 3.2
167 */
168 public void setCountEmpty(boolean countEmpty) {
169 this.countEmpty = countEmpty;
170 }
171
172 }