001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.sizes; 021 022import java.util.ArrayDeque; 023import java.util.BitSet; 024import java.util.Deque; 025import java.util.Objects; 026import java.util.stream.Stream; 027 028import com.puppycrawl.tools.checkstyle.StatelessCheck; 029import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 033 034/** 035 * <div> 036 * Checks for long methods and constructors. 037 * </div> 038 * 039 * <p> 040 * Rationale: If a method becomes very long it is hard to understand. 041 * Therefore, long methods should usually be refactored into several 042 * individual methods that focus on a specific task. 043 * </p> 044 * <ul> 045 * <li> 046 * Property {@code countEmpty} - Control whether to count empty lines and comments. 047 * Type is {@code boolean}. 048 * Default value is {@code true}. 049 * </li> 050 * <li> 051 * Property {@code max} - Specify the maximum number of lines allowed. 052 * Type is {@code int}. 053 * Default value is {@code 150}. 054 * </li> 055 * <li> 056 * Property {@code tokens} - tokens to check 057 * Type is {@code java.lang.String[]}. 058 * Validation type is {@code tokenSet}. 059 * Default value is: 060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 061 * METHOD_DEF</a>, 062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 063 * CTOR_DEF</a>, 064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF"> 065 * COMPACT_CTOR_DEF</a>. 066 * </li> 067 * </ul> 068 * 069 * <p> 070 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 071 * </p> 072 * 073 * <p> 074 * Violation Message Keys: 075 * </p> 076 * <ul> 077 * <li> 078 * {@code maxLen.method} 079 * </li> 080 * </ul> 081 * 082 * @since 3.0 083 */ 084@StatelessCheck 085public class MethodLengthCheck extends AbstractCheck { 086 087 /** 088 * A key is pointing to the warning message text in "messages.properties" 089 * file. 090 */ 091 public static final String MSG_KEY = "maxLen.method"; 092 093 /** Default maximum number of lines. */ 094 private static final int DEFAULT_MAX_LINES = 150; 095 096 /** Control whether to count empty lines and comments. */ 097 private boolean countEmpty = true; 098 099 /** 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}