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