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.indentation;
021
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.TokenTypes;
024import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
025
026/**
027 * Handler for method definitions.
028 *
029 */
030public class MethodDefHandler extends BlockParentHandler {
031
032    /**
033     * Construct an instance of this handler with the given indentation check,
034     * abstract syntax tree, and parent handler.
035     *
036     * @param indentCheck   the indentation check
037     * @param ast           the abstract syntax tree
038     * @param parent        the parent handler
039     */
040    public MethodDefHandler(IndentationCheck indentCheck,
041        DetailAST ast, AbstractExpressionHandler parent) {
042        super(indentCheck, getHandlerName(ast), ast, parent);
043    }
044
045    @Override
046    protected DetailAST getTopLevelAst() {
047        // we check this stuff ourselves below
048        return null;
049    }
050
051    /**
052     * Checks modifiers for method definitions.
053     */
054    private void checkModifiers() {
055        final DetailAST modifier = getMainAst().findFirstToken(TokenTypes.MODIFIERS);
056        if (isOnStartOfLine(modifier)
057            && !getIndent().isAcceptable(expandedTabsColumnNo(modifier))) {
058            logError(modifier, "modifier", expandedTabsColumnNo(modifier));
059        }
060    }
061
062    /**
063     * Check the indentation level of the throws clause.
064     */
065    private void checkThrows() {
066        final DetailAST throwsAst = getMainAst().findFirstToken(TokenTypes.LITERAL_THROWS);
067
068        if (throwsAst != null) {
069            final LineWrappingHandler.LineWrappingOptions ignoreFirstLine;
070            if (isOnStartOfLine(throwsAst)) {
071                ignoreFirstLine = LineWrappingHandler.LineWrappingOptions.NONE;
072            }
073            else {
074                ignoreFirstLine = LineWrappingHandler.LineWrappingOptions.IGNORE_FIRST_LINE;
075            }
076            checkWrappingIndentation(throwsAst, throwsAst.getNextSibling(), getIndentCheck()
077                    .getThrowsIndent(), getLineStart(getMethodDefLineStart(getMainAst())),
078                    ignoreFirstLine);
079        }
080    }
081
082    /**
083     * Gets the start line of the method, excluding any annotations. This is required because the
084     * current {@link TokenTypes#METHOD_DEF} may not always be the start as seen in
085     * <a href="https://github.com/checkstyle/checkstyle/issues/3145">#3145</a>.
086     *
087     * @param mainAst
088     *            The method definition ast.
089     * @return The start column position of the method.
090     */
091    private static int getMethodDefLineStart(DetailAST mainAst) {
092        // get first type position
093        int lineStart = mainAst.findFirstToken(TokenTypes.IDENT).getLineNo();
094
095        // check if there is a type before the indent
096        final DetailAST typeNode = mainAst.findFirstToken(TokenTypes.TYPE);
097        if (typeNode != null) {
098            lineStart = getFirstLine(typeNode);
099        }
100
101        // check if there is a modifier before the type
102        for (DetailAST node = mainAst.findFirstToken(TokenTypes.MODIFIERS).getFirstChild();
103                node != null;
104                node = node.getNextSibling()) {
105            // skip annotations as we check them else where as outside the method
106            if (node.getType() == TokenTypes.ANNOTATION) {
107                continue;
108            }
109
110            lineStart = Math.min(lineStart, node.getLineNo());
111        }
112
113        return lineStart;
114    }
115
116    @Override
117    public void checkIndentation() {
118        checkModifiers();
119        checkThrows();
120
121        if (getMethodDefParamRightParen(getMainAst()) != null) {
122            checkWrappingIndentation(getMainAst(), getMethodDefParamRightParen(getMainAst()));
123        }
124        // abstract method def -- no body
125        if (getLeftCurly() != null) {
126            super.checkIndentation();
127        }
128    }
129
130    @Override
131    protected boolean shouldCheckLeftParen(DetailAST leftParen) {
132        return !isMethodParenOnItsOwnLineWithMatchingRightParen(leftParen,
133                getMethodDefParamRightParen(getMainAst()));
134    }
135
136    @Override
137    protected boolean shouldCheckRightParen(DetailAST leftParen, DetailAST rightParen) {
138        return !isMethodParenOnItsOwnLineWithMatchingRightParen(leftParen, rightParen);
139    }
140
141    /**
142     * Checks if method definition parenthesis are wrapped on separate lines and aligned.
143     *
144     * @param leftParen left parenthesis of method definition
145     * @param rightParen right parenthesis of method definition
146     * @return true if both parenthesis start their own lines and are aligned
147     */
148    private boolean isMethodParenOnItsOwnLineWithMatchingRightParen(DetailAST leftParen,
149            DetailAST rightParen) {
150        return rightParen != null
151                && !TokenUtil.areOnSameLine(leftParen, rightParen)
152                && isOnStartOfLine(leftParen)
153                && isOnStartOfLine(rightParen)
154                && expandedTabsColumnNo(leftParen) == expandedTabsColumnNo(rightParen);
155    }
156
157    /**
158     * Returns right parenthesis of method definition parameter list.
159     *
160     * @param methodDefAst
161     *          method definition ast node(TokenTypes.LITERAL_IF)
162     * @return right parenthesis of method definition parameter list.
163     */
164    private static DetailAST getMethodDefParamRightParen(DetailAST methodDefAst) {
165        return methodDefAst.findFirstToken(TokenTypes.RPAREN);
166    }
167
168    /**
169     * Creates a handler name for this class according to ast type.
170     *
171     * @param ast the abstract syntax tree.
172     * @return handler name for this class.
173     */
174    private static String getHandlerName(DetailAST ast) {
175
176        return switch (ast.getType()) {
177            case TokenTypes.CTOR_DEF -> "ctor def";
178            case TokenTypes.ANNOTATION_FIELD_DEF -> "annotation field def";
179            case TokenTypes.COMPACT_CTOR_DEF -> "compact ctor def";
180            default -> "method def";
181        };
182    }
183
184}