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 * <p> 036 * Checks for long methods and constructors. 037 * </p> 038 * <p> 039 * Rationale: If a method becomes very long it is hard to understand. 040 * Therefore, long methods should usually be refactored into several 041 * individual methods that focus on a specific task. 042 * </p> 043 * <ul> 044 * <li> 045 * Property {@code countEmpty} - Control whether to count empty lines and comments. 046 * Type is {@code boolean}. 047 * Default value is {@code true}. 048 * </li> 049 * <li> 050 * Property {@code max} - Specify the maximum number of lines allowed. 051 * Type is {@code int}. 052 * Default value is {@code 150}. 053 * </li> 054 * <li> 055 * Property {@code tokens} - tokens to check 056 * Type is {@code java.lang.String[]}. 057 * Validation type is {@code tokenSet}. 058 * Default value is: 059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 060 * METHOD_DEF</a>, 061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 062 * CTOR_DEF</a>, 063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF"> 064 * COMPACT_CTOR_DEF</a>. 065 * </li> 066 * </ul> 067 * <p> 068 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 069 * </p> 070 * <p> 071 * Violation Message Keys: 072 * </p> 073 * <ul> 074 * <li> 075 * {@code maxLen.method} 076 * </li> 077 * </ul> 078 * 079 * @since 3.0 080 */ 081@StatelessCheck 082public class MethodLengthCheck extends AbstractCheck { 083 084 /** 085 * A key is pointing to the warning message text in "messages.properties" 086 * file. 087 */ 088 public static final String MSG_KEY = "maxLen.method"; 089 090 /** Default maximum number of lines. */ 091 private static final int DEFAULT_MAX_LINES = 150; 092 093 /** Control whether to count empty lines and comments. */ 094 private boolean countEmpty = true; 095 096 /** Specify the maximum number of lines allowed. */ 097 private int max = DEFAULT_MAX_LINES; 098 099 @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}