View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2015 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  package com.puppycrawl.tools.checkstyle.checks.indentation;
20  
21  import com.puppycrawl.tools.checkstyle.api.DetailAST;
22  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
23  
24  /**
25   * Handler for method calls.
26   *
27   * @author jrichard
28   */
29  public class MethodCallHandler extends ExpressionHandler
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, ExpressionHandler parent)
41      {
42          super(indentCheck,
43              ast.getType() == TokenTypes.METHOD_CALL
44                  ? "method call" : "ctor call",
45              ast,
46              parent);
47      }
48  
49      @Override
50      protected IndentLevel getLevelImpl()
51      {
52          // if inside a method call's params, this could be part of
53          // an expression, so get the previous line's start
54          if (getParent() instanceof MethodCallHandler) {
55              final MethodCallHandler container =
56                      (MethodCallHandler) getParent();
57              if (container != null) {
58                  if (areOnSameLine(container.getMainAst(), getMainAst())) {
59                      return container.getLevel();
60                  }
61  
62                  // we should increase indentation only if this is the first
63                  // chained method call which was moved to the next line
64                  final DetailAST main = getMainAst();
65                  final DetailAST dot = main.getFirstChild();
66                  final DetailAST target = dot.getFirstChild();
67  
68                  if (dot.getType() == TokenTypes.DOT
69                      && target.getType() == TokenTypes.METHOD_CALL)
70                  {
71                      final DetailAST dot1 = target.getFirstChild();
72                      final DetailAST target1 = dot1.getFirstChild();
73  
74                      if (dot1.getType() == TokenTypes.DOT
75                          && target1.getType() == TokenTypes.METHOD_CALL)
76                      {
77                          return container.getLevel();
78                      }
79                  }
80                  return new IndentLevel(container.getLevel(), getBasicOffset());
81              }
82  
83              // if we get here, we are the child of the left hand side (name
84              //  side) of a method call with no "containing" call, use
85              //  the first non-method call parent
86  
87              ExpressionHandler p = getParent();
88              while (p instanceof MethodCallHandler) {
89                  p = p.getParent();
90              }
91              return p.suggestedChildLevel(this);
92          }
93  
94          // if our expression isn't first on the line, just use the start
95          // of the line
96          final LineSet lines = new LineSet();
97          findSubtreeLines(lines, getMainAst().getFirstChild(), true);
98          final int firstCol = lines.firstLineCol();
99          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                     getMethodCallLastNode(getMainAst()));
179         lineWrap.checkIndentation();
180     }
181 
182     @Override
183     protected boolean shouldIncreaseIndent()
184     {
185         return false;
186     }
187 
188     /**
189      * Returns method call right paren.
190      * @param firstNode
191      *          method call ast(TokenTypes.METHOD_CALL)
192      * @return ast node containing right paren for specified method call. If
193      * method calls are chained returns right paren for last call.
194      */
195     private static DetailAST getMethodCallLastNode(DetailAST firstNode)
196     {
197         DetailAST lastNode;
198         if (firstNode.getNextSibling() == null) {
199             lastNode = firstNode.getLastChild();
200         }
201         else {
202             lastNode = firstNode.getNextSibling();
203         }
204         return lastNode;
205     }
206 }