001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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     * https://github.com/checkstyle/checkstyle/issues/3145.
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            if (node.getLineNo() < lineStart) {
101                lineStart = node.getLineNo();
102            }
103        }
104
105        return lineStart;
106    }
107
108    @Override
109    public void checkIndentation() {
110        checkModifiers();
111        checkThrows();
112
113        checkWrappingIndentation(getMainAst(), getMethodDefParamRightParen(getMainAst()));
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        final String name;
139
140        if (ast.getType() == TokenTypes.CTOR_DEF) {
141            name = "ctor def";
142        }
143        else if (ast.getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
144            name = "annotation field def";
145        }
146        else {
147            name = "method def";
148        }
149        return name;
150    }
151
152}