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