View Javadoc
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 }