001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 operator new. 028 */ 029public class NewHandler extends AbstractExpressionHandler { 030 031 /** 032 * Token types that require line wrapping indentation for new keyword. 033 */ 034 private static final int[] LINE_WRAP_NEW_PARENT_TYPES = { 035 TokenTypes.ASSIGN, 036 TokenTypes.LITERAL_RETURN, 037 TokenTypes.LITERAL_THROW, 038 }; 039 040 /** The AST which is handled by this handler. */ 041 private final DetailAST mainAst; 042 043 /** 044 * Construct an instance of this handler with the given indentation check, 045 * abstract syntax tree, and parent handler. 046 * 047 * @param indentCheck the indentation check 048 * @param ast the abstract syntax tree 049 * @param parent the parent handler 050 */ 051 public NewHandler(IndentationCheck indentCheck, 052 DetailAST ast, 053 AbstractExpressionHandler parent) { 054 super(indentCheck, "new", ast, parent); 055 mainAst = ast; 056 } 057 058 @Override 059 public void checkIndentation() { 060 // if new is on the line start and it is not the part of assignment. 061 if (isOnStartOfLine(mainAst)) { 062 final int columnNo = expandedTabsColumnNo(mainAst); 063 final IndentLevel level = getIndentImpl(); 064 065 final boolean forceStrictCondition = getIndentCheck().isForceStrictCondition(); 066 if (forceStrictCondition && !level.isAcceptable(columnNo) 067 || !forceStrictCondition && level.isGreaterThan(columnNo)) { 068 logError(mainAst, "", columnNo, level); 069 } 070 } 071 072 final DetailAST firstChild = mainAst.getFirstChild(); 073 if (firstChild != null) { 074 checkExpressionSubtree(firstChild, getIndent(), false, false); 075 } 076 077 final DetailAST expression = mainAst.findFirstToken(TokenTypes.ELIST); 078 if (checkNestedNew(expression) && isOnStartOfLine(expression)) { 079 final IndentLevel indentLevel = new IndentLevel(getIndent(), 080 getLineWrappingIndent()); 081 checkExpressionSubtree(expression, indentLevel, false, false); 082 } 083 084 final DetailAST lparen = mainAst.findFirstToken(TokenTypes.LPAREN); 085 checkLeftParen(lparen); 086 } 087 088 /** 089 * Check if nested {@code new} present. 090 * 091 * @param expression expression 092 * 093 * @return true if nested new is present. 094 */ 095 public boolean checkNestedNew(DetailAST expression) { 096 boolean result = false; 097 if (expression != null && expression.getFirstChild() != null) { 098 final boolean isNestedNewPresent = expression.getFirstChild() 099 .findFirstToken(TokenTypes.LITERAL_NEW) != null; 100 if (!isNestedNewPresent) { 101 result = true; 102 } 103 } 104 return result; 105 } 106 107 @Override 108 public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) { 109 final int offset; 110 if (TokenUtil.isOfType(child.getMainAst(), TokenTypes.OBJBLOCK)) { 111 offset = getBasicOffset(); 112 } 113 else { 114 offset = getLineWrappingIndent(); 115 } 116 return new IndentLevel(getIndent(), offset); 117 } 118 119 @Override 120 protected IndentLevel getIndentImpl() { 121 IndentLevel result; 122 // if our expression isn't first on the line, just use the start 123 // of the line 124 if (getLineStart(mainAst) == mainAst.getColumnNo()) { 125 result = super.getIndentImpl(); 126 127 final boolean isLineWrappedNew = TokenUtil.isOfType(mainAst.getParent().getParent(), 128 LINE_WRAP_NEW_PARENT_TYPES); 129 130 final int ternaryLevel = getTernaryNestingLevel(); 131 132 if (isLineWrappedNew || doesNewNeedLineWrappingIndent()) { 133 result = new IndentLevel(result, getLineWrappingIndent()); 134 } 135 136 if (ternaryLevel >= 2) { 137 for (int idx = 1; idx < ternaryLevel; idx++) { 138 result = new IndentLevel(result, getLineWrappingIndent()); 139 } 140 } 141 } 142 else { 143 result = new IndentLevel(getLineStart(mainAst)); 144 } 145 146 return result; 147 } 148 149 /** 150 * A shortcut for {@code IndentationCheck} property. 151 * 152 * @return value of lineWrappingIndentation property 153 * of {@code IndentationCheck} 154 */ 155 private int getLineWrappingIndent() { 156 return getIndentCheck().getLineWrappingIndentation(); 157 } 158 159 @Override 160 protected boolean shouldIncreaseIndent() { 161 return false; 162 } 163 164 /** 165 * Checks if the new keyword needs line wrapping indentation. 166 * This applies when new is within an assignment, return, throw, or ternary operator 167 * (where the ternary is part of an assignment, return, or throw statement). 168 * 169 * @return true if the new keyword needs line wrapping indentation 170 */ 171 private boolean doesNewNeedLineWrappingIndent() { 172 DetailAST ast = mainAst.getParent(); 173 174 while (TokenUtil.isOfType(ast, TokenTypes.DOT, TokenTypes.METHOD_CALL, TokenTypes.EXPR)) { 175 ast = ast.getParent(); 176 } 177 178 return TokenUtil.isOfType(ast, LINE_WRAP_NEW_PARENT_TYPES) 179 || ast.getType() == TokenTypes.QUESTION && isParentAssignReturnOrThrow(ast); 180 } 181 182 /** 183 * Checks if the parent of the given AST is an assignment, return or throw statement. 184 * 185 * @param ast the AST node to check 186 * @return true if the parent is ASSIGN, LITERAL_RETURN or LITERAL_THROW 187 */ 188 private static boolean isParentAssignReturnOrThrow(DetailAST ast) { 189 DetailAST parent = ast.getParent(); 190 while (TokenUtil.isOfType(parent, TokenTypes.EXPR, TokenTypes.QUESTION)) { 191 parent = parent.getParent(); 192 } 193 return TokenUtil.isOfType(parent, LINE_WRAP_NEW_PARENT_TYPES); 194 } 195 196 /** 197 * Counts how many ternary operator levels the new keyword is nested in, 198 * where the outermost ternary is part of an assignment, return, or throw. 199 * 200 * @return the number of ternary nesting levels, or 0 if not in a valid ternary context 201 */ 202 private int getTernaryNestingLevel() { 203 DetailAST ast = mainAst.getParent(); 204 205 while (TokenUtil.isOfType(ast, TokenTypes.DOT, TokenTypes.METHOD_CALL, TokenTypes.EXPR)) { 206 ast = ast.getParent(); 207 } 208 209 int level = 0; 210 while (ast.getType() == TokenTypes.QUESTION) { 211 level++; 212 do { 213 ast = ast.getParent(); 214 } while (ast.getType() == TokenTypes.EXPR); 215 } 216 217 int result = 0; 218 if (TokenUtil.isOfType(ast, LINE_WRAP_NEW_PARENT_TYPES)) { 219 result = level; 220 } 221 222 return result; 223 } 224 225}