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