View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.indentation;
21  
22  import com.puppycrawl.tools.checkstyle.api.DetailAST;
23  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
24  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
25  
26  /**
27   * Handler for operator new.
28   */
29  public class NewHandler extends AbstractExpressionHandler {
30  
31      /**
32       * Token types that require line wrapping indentation for new keyword.
33       */
34      private static final int[] LINE_WRAP_NEW_PARENT_TYPES = {
35          TokenTypes.ASSIGN,
36          TokenTypes.LITERAL_RETURN,
37          TokenTypes.LITERAL_THROW,
38      };
39  
40      /** The AST which is handled by this handler. */
41      private final DetailAST mainAst;
42  
43      /**
44       * Construct an instance of this handler with the given indentation check,
45       * abstract syntax tree, and parent handler.
46       *
47       * @param indentCheck   the indentation check
48       * @param ast           the abstract syntax tree
49       * @param parent        the parent handler
50       */
51      public NewHandler(IndentationCheck indentCheck,
52                        DetailAST ast,
53                        AbstractExpressionHandler parent) {
54          super(indentCheck, "new", ast, parent);
55          mainAst = ast;
56      }
57  
58      @Override
59      public void checkIndentation() {
60          // if new is on the line start and it is not the part of assignment.
61          if (isOnStartOfLine(mainAst)) {
62              final int columnNo = expandedTabsColumnNo(mainAst);
63              final IndentLevel level = getIndentImpl();
64  
65              final boolean forceStrictCondition = getIndentCheck().isForceStrictCondition();
66              if (forceStrictCondition && !level.isAcceptable(columnNo)
67                  || !forceStrictCondition && level.isGreaterThan(columnNo)) {
68                  logError(mainAst, "", columnNo, level);
69              }
70          }
71  
72          final DetailAST firstChild = mainAst.getFirstChild();
73          if (firstChild != null) {
74              checkExpressionSubtree(firstChild, getIndent(), false, false);
75          }
76  
77          final DetailAST expression = mainAst.findFirstToken(TokenTypes.ELIST);
78          if (checkNestedNew(expression) && isOnStartOfLine(expression)) {
79              final IndentLevel indentLevel = new IndentLevel(getIndent(),
80                      getLineWrappingIndent());
81              checkExpressionSubtree(expression, indentLevel, false, false);
82          }
83  
84          final DetailAST lparen = mainAst.findFirstToken(TokenTypes.LPAREN);
85          checkLeftParen(lparen);
86      }
87  
88      /**
89       * Check if nested {@code new} present.
90       *
91       * @param expression expression
92       *
93       * @return true if nested new is present.
94       */
95      public boolean checkNestedNew(DetailAST expression) {
96          boolean result = false;
97          if (expression != null && expression.getFirstChild() != null) {
98              final boolean isNestedNewPresent = expression.getFirstChild()
99                  .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 }