1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2019 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.indentation;
21
22 import com.puppycrawl.tools.checkstyle.api.DetailAST;
23 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
24
25 /**
26 * Handler for method calls.
27 *
28 */
29 public class MethodCallHandler extends AbstractExpressionHandler {
30
31 /**
32 * Construct an instance of this handler with the given indentation check,
33 * abstract syntax tree, and parent handler.
34 *
35 * @param indentCheck the indentation check
36 * @param ast the abstract syntax tree
37 * @param parent the parent handler
38 */
39 public MethodCallHandler(IndentationCheck indentCheck,
40 DetailAST ast, AbstractExpressionHandler parent) {
41 super(indentCheck, "method call", ast, parent);
42 }
43
44 @Override
45 protected IndentLevel getIndentImpl() {
46 final IndentLevel indentLevel;
47 // if inside a method call's params, this could be part of
48 // an expression, so get the previous line's start
49 if (getParent() instanceof MethodCallHandler) {
50 final MethodCallHandler container =
51 (MethodCallHandler) getParent();
52 if (areOnSameLine(container.getMainAst(), getMainAst())
53 || isChainedMethodCallWrapped()
54 || areMethodsChained(container.getMainAst(), getMainAst())) {
55 indentLevel = container.getIndent();
56 }
57 // we should increase indentation only if this is the first
58 // chained method call which was moved to the next line
59 else {
60 indentLevel = new IndentLevel(container.getIndent(), getBasicOffset());
61 }
62 }
63 else if (getMainAst().getFirstChild().getType() == TokenTypes.LITERAL_NEW) {
64 indentLevel = super.getIndentImpl();
65 }
66 else {
67 // if our expression isn't first on the line, just use the start
68 // of the line
69 final LineSet lines = new LineSet();
70 findSubtreeLines(lines, getMainAst().getFirstChild(), true);
71 final int firstCol = lines.firstLineCol();
72 final int lineStart = getLineStart(getFirstAst(getMainAst()));
73 if (lineStart == firstCol) {
74 indentLevel = super.getIndentImpl();
75 }
76 else {
77 indentLevel = new IndentLevel(lineStart);
78 }
79 }
80 return indentLevel;
81 }
82
83 /**
84 * Checks if ast2 is a chained method call that starts on the same level as ast1 ends.
85 * In other words, if the right paren of ast1 is on the same level as the lparen of ast2:
86 *
87 * {@code
88 * value.methodOne(
89 * argument1
90 * ).methodTwo(
91 * argument2
92 * );
93 * }
94 *
95 * @param ast1 Ast1
96 * @param ast2 Ast2
97 * @return True if ast2 begins on the same level that ast1 ends
98 */
99 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 final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN);
170 IndentLevel suggestedLevel = new IndentLevel(getLineStart(ident));
171 if (!areOnSameLine(child.getMainAst().getFirstChild(), ident)) {
172 suggestedLevel = new IndentLevel(suggestedLevel,
173 getBasicOffset(),
174 getIndentCheck().getLineWrappingIndentation());
175 }
176
177 // If the right parenthesis is at the start of a line;
178 // include line wrapping in suggested indent level.
179 if (getLineStart(rparen) == rparen.getColumnNo()) {
180 suggestedLevel = IndentLevel.addAcceptable(suggestedLevel, 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 }