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