001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 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 * @author jrichard 029 */ 030public class MethodCallHandler extends AbstractExpressionHandler { 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 indentLevel = container.getIndent(); 055 } 056 // we should increase indentation only if this is the first 057 // chained method call which was moved to the next line 058 else { 059 indentLevel = new IndentLevel(container.getIndent(), getBasicOffset()); 060 } 061 } 062 else { 063 // if our expression isn't first on the line, just use the start 064 // of the line 065 final LineSet lines = new LineSet(); 066 findSubtreeLines(lines, getMainAst().getFirstChild(), true); 067 final int firstCol = lines.firstLineCol(); 068 final int lineStart = getLineStart(getFirstAst(getMainAst())); 069 if (lineStart == firstCol) { 070 indentLevel = super.getIndentImpl(); 071 } 072 else { 073 indentLevel = new IndentLevel(lineStart); 074 } 075 } 076 return indentLevel; 077 } 078 079 /** 080 * If this is the first chained method call which was moved to the next line. 081 * @return true if chained class are wrapped 082 */ 083 private boolean isChainedMethodCallWrapped() { 084 boolean result = false; 085 final DetailAST main = getMainAst(); 086 final DetailAST dot = main.getFirstChild(); 087 final DetailAST target = dot.getFirstChild(); 088 089 final DetailAST dot1 = target.getFirstChild(); 090 final DetailAST target1 = dot1.getFirstChild(); 091 092 if (dot1.getType() == TokenTypes.DOT 093 && target1.getType() == TokenTypes.METHOD_CALL) { 094 result = true; 095 } 096 return result; 097 } 098 099 /** 100 * Get the first AST of the specified method call. 101 * 102 * @param ast 103 * the method call 104 * 105 * @return the first AST of the specified method call 106 */ 107 private static DetailAST getFirstAst(DetailAST ast) { 108 // walk down the first child part of the dots that make up a method 109 // call name 110 111 DetailAST astNode = ast.getFirstChild(); 112 while (astNode.getType() == TokenTypes.DOT) { 113 astNode = astNode.getFirstChild(); 114 } 115 return astNode; 116 } 117 118 @Override 119 public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) { 120 // for whatever reason a method that crosses lines, like asList 121 // here: 122 // System.out.println("methods are: " + Arrays.asList( 123 // new String[] {"method"}).toString()); 124 // will not have the right line num, so just get the child name 125 126 final DetailAST first = getMainAst().getFirstChild(); 127 IndentLevel suggestedLevel = new IndentLevel(getLineStart(first)); 128 if (!areOnSameLine(child.getMainAst().getFirstChild(), 129 getMainAst().getFirstChild())) { 130 suggestedLevel = new IndentLevel(suggestedLevel, 131 getBasicOffset(), 132 getIndentCheck().getLineWrappingIndentation()); 133 } 134 135 // If the right parenthesis is at the start of a line; 136 // include line wrapping in suggested indent level. 137 final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN); 138 if (getLineStart(rparen) == rparen.getColumnNo()) { 139 suggestedLevel.addAcceptedIndent(new IndentLevel( 140 getParent().getSuggestedChildIndent(this), 141 getIndentCheck().getLineWrappingIndentation() 142 )); 143 } 144 145 return suggestedLevel; 146 } 147 148 @Override 149 public void checkIndentation() { 150 final DetailAST exprNode = getMainAst().getParent(); 151 if (exprNode.getParent().getType() == TokenTypes.SLIST) { 152 final DetailAST methodName = getMainAst().getFirstChild(); 153 checkExpressionSubtree(methodName, getIndent(), false, false); 154 155 final DetailAST lparen = getMainAst(); 156 final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN); 157 checkLParen(lparen); 158 159 if (rparen.getLineNo() != lparen.getLineNo()) { 160 checkExpressionSubtree( 161 getMainAst().findFirstToken(TokenTypes.ELIST), 162 new IndentLevel(getIndent(), getBasicOffset()), 163 false, true); 164 165 checkRParen(lparen, rparen); 166 checkWrappingIndentation(getMainAst(), getMethodCallLastNode(getMainAst())); 167 } 168 } 169 } 170 171 @Override 172 protected boolean shouldIncreaseIndent() { 173 return false; 174 } 175 176 /** 177 * Returns method call right paren. 178 * @param firstNode 179 * method call ast(TokenTypes.METHOD_CALL) 180 * @return ast node containing right paren for specified method call. If 181 * method calls are chained returns right paren for last call. 182 */ 183 private static DetailAST getMethodCallLastNode(DetailAST firstNode) { 184 return firstNode.getLastChild(); 185 } 186}