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