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 }