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