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