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