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