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 context, 045 * abstract syntax tree, and parent handler. 046 * 047 * @param context the indentation check 048 * @param ast the abstract syntax tree 049 * @param parent the parent handler 050 */ 051 public NewHandler(IndentationContext context, 052 DetailAST ast, 053 AbstractExpressionHandler parent) { 054 super(context, "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 = getContext().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 (isOnStartOfLine(mainAst)) { 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 the corresponding property. 151 * 152 * @return value of lineWrappingIndentation property from the indentation context 153 */ 154 private int getLineWrappingIndent() { 155 return getContext().getLineWrappingIndentation(); 156 } 157 158 @Override 159 protected boolean shouldIncreaseIndent() { 160 return false; 161 } 162 163 /** 164 * Checks if the new keyword needs line wrapping indentation. 165 * This applies when new is within an assignment, return, throw, or ternary operator 166 * (where the ternary is part of an assignment, return, or throw statement). 167 * 168 * @return true if the new keyword needs line wrapping indentation 169 */ 170 private boolean doesNewNeedLineWrappingIndent() { 171 DetailAST ast = mainAst.getParent(); 172 173 while (TokenUtil.isOfType(ast, TokenTypes.DOT, TokenTypes.METHOD_CALL, TokenTypes.EXPR)) { 174 ast = ast.getParent(); 175 } 176 177 return TokenUtil.isOfType(ast, LINE_WRAP_NEW_PARENT_TYPES) 178 || ast.getType() == TokenTypes.QUESTION && isParentAssignReturnOrThrow(ast); 179 } 180 181 /** 182 * Checks if the parent of the given AST is an assignment, return or throw statement. 183 * 184 * @param ast the AST node to check 185 * @return true if the parent is ASSIGN, LITERAL_RETURN or LITERAL_THROW 186 */ 187 private static boolean isParentAssignReturnOrThrow(DetailAST ast) { 188 DetailAST parent = ast.getParent(); 189 while (TokenUtil.isOfType(parent, TokenTypes.EXPR, TokenTypes.QUESTION)) { 190 parent = parent.getParent(); 191 } 192 return TokenUtil.isOfType(parent, LINE_WRAP_NEW_PARENT_TYPES); 193 } 194 195 /** 196 * Counts how many ternary operator levels the new keyword is nested in, 197 * where the outermost ternary is part of an assignment, return, or throw. 198 * 199 * @return the number of ternary nesting levels, or 0 if not in a valid ternary context 200 */ 201 private int getTernaryNestingLevel() { 202 DetailAST ast = mainAst.getParent(); 203 204 while (TokenUtil.isOfType(ast, TokenTypes.DOT, TokenTypes.METHOD_CALL, TokenTypes.EXPR)) { 205 ast = ast.getParent(); 206 } 207 208 int level = 0; 209 while (ast.getType() == TokenTypes.QUESTION) { 210 level++; 211 do { 212 ast = ast.getParent(); 213 } while (ast.getType() == TokenTypes.EXPR); 214 } 215 216 int result = 0; 217 if (TokenUtil.isOfType(ast, LINE_WRAP_NEW_PARENT_TYPES)) { 218 result = level; 219 } 220 221 return result; 222 } 223 224}