001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.indentation;
020
021import com.puppycrawl.tools.checkstyle.api.DetailAST;
022import com.puppycrawl.tools.checkstyle.api.TokenTypes;
023
024/**
025 * Handler for method calls.
026 *
027 * @author jrichard
028 */
029public class MethodCallHandler extends ExpressionHandler
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, ExpressionHandler parent)
041    {
042        super(indentCheck,
043            ast.getType() == TokenTypes.METHOD_CALL
044                ? "method call" : "ctor call",
045            ast,
046            parent);
047    }
048
049    @Override
050    protected IndentLevel getLevelImpl()
051    {
052        // if inside a method call's params, this could be part of
053        // an expression, so get the previous line's start
054        if (getParent() instanceof MethodCallHandler) {
055            final MethodCallHandler container =
056                ((MethodCallHandler) getParent());
057            if (container != null) {
058                if (areOnSameLine(container.getMainAst(), getMainAst())) {
059                    return container.getLevel();
060                }
061
062                // we should increase indentation only if this is the first
063                // chained method call which was moved to the next line
064                final DetailAST main = getMainAst();
065                final DetailAST dot = main.getFirstChild();
066                final DetailAST target = dot.getFirstChild();
067
068                if ((dot.getType() == TokenTypes.DOT)
069                    && (target.getType() == TokenTypes.METHOD_CALL))
070                {
071                    final DetailAST dot1 = target.getFirstChild();
072                    final DetailAST target1 = dot1.getFirstChild();
073
074                    if ((dot1.getType() == TokenTypes.DOT)
075                        && (target1.getType() == TokenTypes.METHOD_CALL))
076                    {
077                        return container.getLevel();
078                    }
079                }
080                return new IndentLevel(container.getLevel(), getBasicOffset());
081            }
082
083            // if we get here, we are the child of the left hand side (name
084            //  side) of a method call with no "containing" call, use
085            //  the first non-method call parent
086
087            ExpressionHandler p = getParent();
088            while (p instanceof MethodCallHandler) {
089                p = p.getParent();
090            }
091            return p.suggestedChildLevel(this);
092        }
093
094        // if our expression isn't first on the line, just use the start
095        // of the line
096        final LineSet lines = new LineSet();
097        findSubtreeLines(lines, getMainAst().getFirstChild(), true);
098        final int firstCol = lines.firstLineCol();
099        final int lineStart = getLineStart(getFirstAst(getMainAst()));
100        if (lineStart != firstCol) {
101            return new IndentLevel(lineStart);
102        }
103        return super.getLevelImpl();
104    }
105
106    /**
107     * Get the first AST of the specified method call.
108     *
109     * @param ast
110     *            the method call
111     *
112     * @return the first AST of the specified method call
113     */
114    private DetailAST getFirstAst(DetailAST ast)
115    {
116        // walk down the first child part of the dots that make up a method
117        // call name
118
119        DetailAST astNode = ast.getFirstChild();
120        while ((astNode != null) && (astNode.getType() == TokenTypes.DOT)) {
121            astNode = astNode.getFirstChild();
122        }
123
124        if (astNode == null) {
125            astNode = ast;
126        }
127
128        return astNode;
129    }
130
131    @Override
132    public IndentLevel suggestedChildLevel(ExpressionHandler child)
133    {
134        // for whatever reason a method that crosses lines, like asList
135        // here:
136        //            System.out.println("methods are: " + Arrays.asList(
137        //                new String[] {"method"}).toString());
138        // will not have the right line num, so just get the child name
139
140        final DetailAST first = getMainAst().getFirstChild();
141        int indentLevel = getLineStart(first);
142        if (!areOnSameLine(child.getMainAst().getFirstChild(),
143                           getMainAst().getFirstChild()))
144        {
145            indentLevel += getBasicOffset();
146        }
147        return new IndentLevel(indentLevel);
148    }
149
150    @Override
151    public void checkIndentation()
152    {
153        final DetailAST exprNode = getMainAst().getParent();
154        if (exprNode.getParent().getType() != TokenTypes.LCURLY
155            && exprNode.getParent().getType() != TokenTypes.SLIST)
156        {
157            return;
158        }
159        final DetailAST methodName = getMainAst().getFirstChild();
160        checkExpressionSubtree(methodName, getLevel(), false, false);
161
162        final DetailAST lparen = getMainAst();
163        final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN);
164        checkLParen(lparen);
165
166        if (rparen.getLineNo() == lparen.getLineNo()) {
167            return;
168        }
169
170        checkExpressionSubtree(
171            getMainAst().findFirstToken(TokenTypes.ELIST),
172            new IndentLevel(getLevel(), getBasicOffset()),
173            false, true);
174
175        checkRParen(lparen, rparen);
176        final LineWrappingHandler lineWrap =
177            new LineWrappingHandler(getIndentCheck(), getMainAst()) {
178                @Override
179                public DetailAST findLastNode(DetailAST firstNode)
180                {
181                    DetailAST lastNode;
182                    if (getFirstNode().getNextSibling() == null) {
183                        lastNode = getFirstNode().getLastChild();
184                    }
185                    else {
186                        lastNode = getFirstNode().getNextSibling();
187                    }
188                    return lastNode;
189                }
190
191                @Override
192                public int getCurrentIndentation()
193                {
194                    DetailAST curNode = getFirstNode();
195                    while (curNode.getType() != TokenTypes.IDENT) {
196                        curNode = curNode.getFirstChild();
197                    }
198                    return curNode.getColumnNo() + getIndentLevel();
199                }
200            };
201        lineWrap.checkIndentation();
202    }
203
204    @Override
205    protected boolean shouldIncreaseIndent()
206    {
207        return false;
208    }
209}