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   * <p>
36   * Checks for long methods and constructors.
37   * </p>
38   * <p>
39   * Rationale: If a method becomes very long it is hard to understand.
40   * Therefore, long methods should usually be refactored into several
41   * individual methods that focus on a specific task.
42   * </p>
43   * <ul>
44   * <li>
45   * Property {@code countEmpty} - Control whether to count empty lines and comments.
46   * Type is {@code boolean}.
47   * Default value is {@code true}.
48   * </li>
49   * <li>
50   * Property {@code max} - Specify the maximum number of lines allowed.
51   * Type is {@code int}.
52   * Default value is {@code 150}.
53   * </li>
54   * <li>
55   * Property {@code tokens} - tokens to check
56   * Type is {@code java.lang.String[]}.
57   * Validation type is {@code tokenSet}.
58   * Default value is:
59   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
60   * METHOD_DEF</a>,
61   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
62   * CTOR_DEF</a>,
63   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
64   * COMPACT_CTOR_DEF</a>.
65   * </li>
66   * </ul>
67   * <p>
68   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
69   * </p>
70   * <p>
71   * Violation Message Keys:
72   * </p>
73   * <ul>
74   * <li>
75   * {@code maxLen.method}
76   * </li>
77   * </ul>
78   *
79   * @since 3.0
80   */
81  @StatelessCheck
82  public class MethodLengthCheck extends AbstractCheck {
83  
84      /**
85       * A key is pointing to the warning message text in "messages.properties"
86       * file.
87       */
88      public static final String MSG_KEY = "maxLen.method";
89  
90      /** Default maximum number of lines. */
91      private static final int DEFAULT_MAX_LINES = 150;
92  
93      /** Control whether to count empty lines and comments. */
94      private boolean countEmpty = true;
95  
96      /** Specify the maximum number of lines allowed. */
97      private int max = DEFAULT_MAX_LINES;
98  
99      @Override
100     public int[] getDefaultTokens() {
101         return getAcceptableTokens();
102     }
103 
104     @Override
105     public int[] getAcceptableTokens() {
106         return new int[] {
107             TokenTypes.METHOD_DEF,
108             TokenTypes.CTOR_DEF,
109             TokenTypes.COMPACT_CTOR_DEF,
110         };
111     }
112 
113     @Override
114     public int[] getRequiredTokens() {
115         return CommonUtil.EMPTY_INT_ARRAY;
116     }
117 
118     @Override
119     public void visitToken(DetailAST ast) {
120         final DetailAST openingBrace = ast.findFirstToken(TokenTypes.SLIST);
121         if (openingBrace != null) {
122             final int length;
123             if (countEmpty) {
124                 final DetailAST closingBrace = openingBrace.findFirstToken(TokenTypes.RCURLY);
125                 length = getLengthOfBlock(openingBrace, closingBrace);
126             }
127             else {
128                 length = countUsedLines(openingBrace);
129             }
130             if (length > max) {
131                 final String methodName = ast.findFirstToken(TokenTypes.IDENT).getText();
132                 log(ast, MSG_KEY, length, max, methodName);
133             }
134         }
135     }
136 
137     /**
138      * Returns length of code.
139      *
140      * @param openingBrace block opening brace
141      * @param closingBrace block closing brace
142      * @return number of lines with code for current block
143      */
144     private static int getLengthOfBlock(DetailAST openingBrace, DetailAST closingBrace) {
145         final int startLineNo = openingBrace.getLineNo();
146         final int endLineNo = closingBrace.getLineNo();
147         return endLineNo - startLineNo + 1;
148     }
149 
150     /**
151      * Count number of used code lines without comments.
152      *
153      * @param ast start ast
154      * @return number of used lines of code
155      */
156     private static int countUsedLines(DetailAST ast) {
157         final Deque<DetailAST> nodes = new ArrayDeque<>();
158         nodes.add(ast);
159         final BitSet usedLines = new BitSet();
160         while (!nodes.isEmpty()) {
161             final DetailAST node = nodes.removeFirst();
162             final int lineIndex = node.getLineNo();
163             // text block requires special treatment,
164             // since it is the only non-comment token that can span more than one line
165             if (node.getType() == TokenTypes.TEXT_BLOCK_LITERAL_BEGIN) {
166                 final int endLineIndex = node.getLastChild().getLineNo();
167                 usedLines.set(lineIndex, endLineIndex + 1);
168             }
169             else {
170                 usedLines.set(lineIndex);
171                 Stream.iterate(
172                     node.getLastChild(), Objects::nonNull, DetailAST::getPreviousSibling
173                 ).forEach(nodes::addFirst);
174             }
175         }
176         return usedLines.cardinality();
177     }
178 
179     /**
180      * Setter to specify the maximum number of lines allowed.
181      *
182      * @param length the maximum length of a method.
183      * @since 3.0
184      */
185     public void setMax(int length) {
186         max = length;
187     }
188 
189     /**
190      * Setter to control whether to count empty lines and comments.
191      *
192      * @param countEmpty whether to count empty and comments.
193      * @since 3.2
194      */
195     public void setCountEmpty(boolean countEmpty) {
196         this.countEmpty = countEmpty;
197     }
198 
199 }