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}