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}