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