001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 * 045 * @since 3.0 046 */ 047@StatelessCheck 048public class MethodLengthCheck extends AbstractCheck { 049 050 /** 051 * A key is pointing to the warning message text in "messages.properties" 052 * file. 053 */ 054 public static final String MSG_KEY = "maxLen.method"; 055 056 /** Default maximum number of lines. */ 057 private static final int DEFAULT_MAX_LINES = 150; 058 059 /** Control whether to count empty lines and comments. */ 060 private boolean countEmpty = true; 061 062 /** Specify the maximum number of lines allowed. */ 063 private int max = DEFAULT_MAX_LINES; 064 065 @Override 066 public int[] getDefaultTokens() { 067 return getAcceptableTokens(); 068 } 069 070 @Override 071 public int[] getAcceptableTokens() { 072 return new int[] { 073 TokenTypes.METHOD_DEF, 074 TokenTypes.CTOR_DEF, 075 TokenTypes.COMPACT_CTOR_DEF, 076 }; 077 } 078 079 @Override 080 public int[] getRequiredTokens() { 081 return CommonUtil.EMPTY_INT_ARRAY; 082 } 083 084 @Override 085 public void visitToken(DetailAST ast) { 086 final DetailAST openingBrace = ast.findFirstToken(TokenTypes.SLIST); 087 if (openingBrace != null) { 088 final int length; 089 if (countEmpty) { 090 final DetailAST closingBrace = openingBrace.findFirstToken(TokenTypes.RCURLY); 091 length = getLengthOfBlock(openingBrace, closingBrace); 092 } 093 else { 094 length = countUsedLines(openingBrace); 095 } 096 if (length > max) { 097 final String methodName = ast.findFirstToken(TokenTypes.IDENT).getText(); 098 log(ast, MSG_KEY, length, max, methodName); 099 } 100 } 101 } 102 103 /** 104 * Returns length of code. 105 * 106 * @param openingBrace block opening brace 107 * @param closingBrace block closing brace 108 * @return number of lines with code for current block 109 */ 110 private static int getLengthOfBlock(DetailAST openingBrace, DetailAST closingBrace) { 111 final int startLineNo = openingBrace.getLineNo(); 112 final int endLineNo = closingBrace.getLineNo(); 113 return endLineNo - startLineNo + 1; 114 } 115 116 /** 117 * Count number of used code lines without comments. 118 * 119 * @param ast start ast 120 * @return number of used lines of code 121 */ 122 private static int countUsedLines(DetailAST ast) { 123 final Deque<DetailAST> nodes = new ArrayDeque<>(); 124 nodes.add(ast); 125 final BitSet usedLines = new BitSet(); 126 while (!nodes.isEmpty()) { 127 final DetailAST node = nodes.removeFirst(); 128 final int lineIndex = node.getLineNo(); 129 // text block requires special treatment, 130 // since it is the only non-comment token that can span more than one line 131 if (node.getType() == TokenTypes.TEXT_BLOCK_LITERAL_BEGIN) { 132 final int endLineIndex = node.getLastChild().getLineNo(); 133 usedLines.set(lineIndex, endLineIndex + 1); 134 } 135 else { 136 usedLines.set(lineIndex); 137 Stream.iterate( 138 node.getLastChild(), Objects::nonNull, DetailAST::getPreviousSibling 139 ).forEach(nodes::addFirst); 140 } 141 } 142 return usedLines.cardinality(); 143 } 144 145 /** 146 * Setter to specify the maximum number of lines allowed. 147 * 148 * @param length the maximum length of a method. 149 * @since 3.0 150 */ 151 public void setMax(int length) { 152 max = length; 153 } 154 155 /** 156 * Setter to control whether to count empty lines and comments. 157 * 158 * @param countEmpty whether to count empty and comments. 159 * @since 3.2 160 */ 161 public void setCountEmpty(boolean countEmpty) { 162 this.countEmpty = countEmpty; 163 } 164 165}