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