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;
024import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
025
026/**
027 * Handler for method calls.
028 *
029 */
030public class MethodCallHandler extends AbstractExpressionHandler {
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 MethodCallHandler(IndentationCheck indentCheck,
041        DetailAST ast, AbstractExpressionHandler parent) {
042        super(indentCheck, "method call", ast, parent);
043    }
044
045    @Override
046    protected IndentLevel getIndentImpl() {
047        final IndentLevel indentLevel;
048        // if inside a method call's params, this could be part of
049        // an expression, so get the previous line's start
050        if (getParent() instanceof MethodCallHandler) {
051            final MethodCallHandler container =
052                    (MethodCallHandler) getParent();
053            if (TokenUtil.areOnSameLine(container.getMainAst(), getMainAst())
054                    || isChainedMethodCallWrapped()
055                    || areMethodsChained(container.getMainAst(), getMainAst())) {
056                indentLevel = container.getIndent();
057            }
058            // we should increase indentation only if this is the first
059            // chained method call which was moved to the next line
060            else {
061                indentLevel = new IndentLevel(container.getIndent(),
062                    getIndentCheck().getLineWrappingIndentation());
063            }
064        }
065        else if (getMainAst().getFirstChild().getType() == TokenTypes.LITERAL_NEW) {
066            indentLevel = super.getIndentImpl();
067        }
068        else {
069            // if our expression isn't first on the line, just use the start
070            // of the line
071            final LineSet lines = new LineSet();
072            findSubtreeLines(lines, getMainAst().getFirstChild(), true);
073            final int firstCol = lines.firstLineCol();
074            final int lineStart = getLineStart(getFirstAst(getMainAst()));
075            if (lineStart == firstCol) {
076                indentLevel = super.getIndentImpl();
077            }
078            else {
079                indentLevel = new IndentLevel(lineStart);
080            }
081        }
082        return indentLevel;
083    }
084
085    /**
086     * Checks if ast2 is a chained method call that starts on the same level as ast1 ends.
087     * In other words, if the right paren of ast1 is on the same level as the lparen of ast2:
088     *
089     * {@code
090     *     value.methodOne(
091     *         argument1
092     *     ).methodTwo(
093     *         argument2
094     *     );
095     * }
096     *
097     * @param ast1 Ast1
098     * @param ast2 Ast2
099     * @return True if ast2 begins on the same level that ast1 ends
100     */
101    private static boolean areMethodsChained(DetailAST ast1, DetailAST ast2) {
102        final DetailAST rparen = ast1.findFirstToken(TokenTypes.RPAREN);
103        return TokenUtil.areOnSameLine(rparen, ast2);
104    }
105
106    /**
107     * If this is the first chained method call which was moved to the next line.
108     *
109     * @return true if chained class are wrapped
110     */
111    private boolean isChainedMethodCallWrapped() {
112        boolean result = false;
113        final DetailAST main = getMainAst();
114        final DetailAST dot = main.getFirstChild();
115        final DetailAST target = dot.getFirstChild();
116
117        final DetailAST dot1 = target.getFirstChild();
118        final DetailAST target1 = dot1.getFirstChild();
119
120        if (dot1.getType() == TokenTypes.DOT
121            && target1.getType() == TokenTypes.METHOD_CALL) {
122            result = true;
123        }
124        return result;
125    }
126
127    /**
128     * Get the first AST of the specified method call.
129     *
130     * @param ast
131     *            the method call
132     *
133     * @return the first AST of the specified method call
134     */
135    private static DetailAST getFirstAst(DetailAST ast) {
136        // walk down the first child part of the dots that make up a method
137        // call name
138
139        DetailAST astNode = ast.getFirstChild();
140        while (astNode.getType() == TokenTypes.DOT) {
141            astNode = astNode.getFirstChild();
142        }
143        return astNode;
144    }
145
146    /**
147     * Returns method or constructor name. For {@code foo(arg)} it is `foo`, for
148     *     {@code foo.bar(arg)} it is `bar` for {@code super(arg)} it is 'super'.
149     *
150     * @return TokenTypes.IDENT node for a method call, TokenTypes.SUPER_CTOR_CALL otherwise.
151     */
152    private DetailAST getMethodIdentAst() {
153        DetailAST ast = getMainAst();
154        if (ast.getType() != TokenTypes.SUPER_CTOR_CALL) {
155            ast = ast.getFirstChild();
156            if (ast.getType() == TokenTypes.DOT) {
157                ast = ast.getLastChild();
158            }
159        }
160        return ast;
161    }
162
163    @Override
164    public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) {
165        // for whatever reason a method that crosses lines, like asList
166        // here:
167        //            System.out.println("methods are: " + Arrays.asList(
168        //                new String[] {"method"}).toString());
169        // will not have the right line num, so just get the child name
170
171        final DetailAST ident = getMethodIdentAst();
172        final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN);
173        IndentLevel suggestedLevel = new IndentLevel(getLineStart(ident));
174        if (!TokenUtil.areOnSameLine(child.getMainAst().getFirstChild(), ident)) {
175            suggestedLevel = new IndentLevel(suggestedLevel,
176                    getBasicOffset(),
177                    getIndentCheck().getLineWrappingIndentation());
178        }
179
180        // If the right parenthesis is at the start of a line;
181        // include line wrapping in suggested indent level.
182        if (getLineStart(rparen) == rparen.getColumnNo()) {
183            suggestedLevel = IndentLevel.addAcceptable(suggestedLevel, new IndentLevel(
184                    getParent().getSuggestedChildIndent(this),
185                    getIndentCheck().getLineWrappingIndentation()
186            ));
187        }
188
189        return suggestedLevel;
190    }
191
192    @Override
193    public void checkIndentation() {
194        DetailAST lparen = null;
195        if (getMainAst().getType() == TokenTypes.METHOD_CALL) {
196            final DetailAST exprNode = getMainAst().getParent();
197            if (exprNode.getParent().getType() == TokenTypes.SLIST) {
198                checkExpressionSubtree(getMainAst().getFirstChild(), getIndent(), false, false);
199                lparen = getMainAst();
200            }
201        }
202        else {
203            // TokenTypes.CTOR_CALL|TokenTypes.SUPER_CTOR_CALL
204            lparen = getMainAst().getFirstChild();
205        }
206
207        if (lparen != null) {
208            final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN);
209            checkLeftParen(lparen);
210
211            if (!TokenUtil.areOnSameLine(rparen, lparen)) {
212                checkExpressionSubtree(
213                    getMainAst().findFirstToken(TokenTypes.ELIST),
214                    new IndentLevel(getIndent(), getBasicOffset()),
215                    false, true);
216
217                checkRightParen(lparen, rparen);
218                checkWrappingIndentation(getMainAst(), getCallLastNode(getMainAst()));
219            }
220        }
221    }
222
223    @Override
224    protected boolean shouldIncreaseIndent() {
225        return false;
226    }
227
228    /**
229     * Returns method or constructor call right paren.
230     *
231     * @param firstNode
232     *          call ast(TokenTypes.METHOD_CALL|TokenTypes.CTOR_CALL|TokenTypes.SUPER_CTOR_CALL)
233     * @return ast node containing right paren for specified method or constructor call. If
234     *     method calls are chained returns right paren for last call.
235     */
236    private static DetailAST getCallLastNode(DetailAST firstNode) {
237        return firstNode.getLastChild();
238    }
239
240}