View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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 parents of blocks ('if', 'else', 'while', etc).
28   *
29   * <P>
30   * The "block" handler classes use a common superclass BlockParentHandler,
31   * employing the Template Method pattern.
32   * </P>
33   *
34   * <UL>
35   *   <LI>template method to get the lcurly</LI>
36   *   <LI>template method to get the rcurly</LI>
37   *   <LI>if curlies aren't present, then template method to get expressions
38   *       is called</LI>
39   *   <LI>now all the repetitious code which checks for BOL, if curlies are on
40   *       same line, etc. can be collapsed into the superclass</LI>
41   * </UL>
42   *
43   */
44  public class BlockParentHandler extends AbstractExpressionHandler {
45  
46      /**
47       * Children checked by parent handlers.
48       */
49      private static final int[] CHECKED_CHILDREN = {
50          TokenTypes.VARIABLE_DEF,
51          TokenTypes.EXPR,
52          TokenTypes.ANNOTATION,
53          TokenTypes.OBJBLOCK,
54          TokenTypes.LITERAL_BREAK,
55          TokenTypes.LITERAL_RETURN,
56          TokenTypes.LITERAL_THROW,
57          TokenTypes.LITERAL_CONTINUE,
58          TokenTypes.CTOR_CALL,
59          TokenTypes.SUPER_CTOR_CALL,
60      };
61  
62      /**
63       * Construct an instance of this handler with the given indentation check,
64       * name, abstract syntax tree, and parent handler.
65       *
66       * @param indentCheck   the indentation check
67       * @param name          the name of the handler
68       * @param ast           the abstract syntax tree
69       * @param parent        the parent handler
70       * @noinspection WeakerAccess
71       * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
72       */
73      public BlockParentHandler(IndentationCheck indentCheck,
74          String name, DetailAST ast, AbstractExpressionHandler parent) {
75          super(indentCheck, name, ast, parent);
76      }
77  
78      /**
79       * Returns array of token types which should be checked among children.
80       *
81       * @return array of token types to check.
82       */
83      protected int[] getCheckedChildren() {
84          return CHECKED_CHILDREN.clone();
85      }
86  
87      /**
88       * Get the top level expression being managed by this handler.
89       *
90       * @return the top level expression
91       */
92      protected DetailAST getTopLevelAst() {
93          return getMainAst();
94      }
95  
96      /**
97       * Check the indent of the top level token.
98       */
99      protected void checkTopLevelToken() {
100         final DetailAST topLevel = getTopLevelAst();
101 
102         if (topLevel != null
103                 && !getIndent().isAcceptable(expandedTabsColumnNo(topLevel))
104                 && isOnStartOfLine(topLevel)) {
105             logError(topLevel, "", expandedTabsColumnNo(topLevel));
106         }
107     }
108 
109     /**
110      * Determines if this block expression has curly braces.
111      *
112      * @return true if curly braces are present, false otherwise
113      */
114     private boolean hasCurlies() {
115         return getLeftCurly() != null && getRightCurly() != null;
116     }
117 
118     /**
119      * Get the left curly brace portion of the expression we are handling.
120      *
121      * @return the left curly brace expression
122      */
123     protected DetailAST getLeftCurly() {
124         return getMainAst().findFirstToken(TokenTypes.SLIST);
125     }
126 
127     /**
128      * Get the right curly brace portion of the expression we are handling.
129      *
130      * @return the right curly brace expression
131      */
132     protected DetailAST getRightCurly() {
133         final DetailAST slist = getMainAst().findFirstToken(TokenTypes.SLIST);
134         return slist.findFirstToken(TokenTypes.RCURLY);
135     }
136 
137     /**
138      * Check the indentation of the left curly brace.
139      */
140     private void checkLeftCurly() {
141         // the lcurly can either be at the correct indentation, or nested
142         // with a previous expression
143         final DetailAST lcurly = getLeftCurly();
144         final int lcurlyPos = expandedTabsColumnNo(lcurly);
145 
146         if (!curlyIndent().isAcceptable(lcurlyPos) && isOnStartOfLine(lcurly)) {
147             logError(lcurly, "lcurly", lcurlyPos, curlyIndent());
148         }
149     }
150 
151     /**
152      * Get the expected indentation level for the curly braces.
153      *
154      * @return the curly brace indentation level
155      */
156     protected IndentLevel curlyIndent() {
157         final DetailAST lcurly = getLeftCurly();
158         IndentLevel expIndentLevel = new IndentLevel(getIndent(), getBraceAdjustment());
159         if (!isOnStartOfLine(lcurly) || checkIfCodeBlock()) {
160             expIndentLevel = new IndentLevel(getIndent(), 0);
161         }
162         return expIndentLevel;
163     }
164 
165     /**
166      * Checks if lcurly is a Code block.
167      *
168      * @return true if lcurly is a code block
169      */
170     private boolean checkIfCodeBlock() {
171         return getMainAst().getType() == TokenTypes.SLIST
172                 && getParent() instanceof BlockParentHandler
173                 && getParent().getParent() instanceof BlockParentHandler;
174     }
175 
176     /**
177      * Determines if child elements within the expression may be nested.
178      *
179      * @return false
180      */
181     protected boolean canChildrenBeNested() {
182         return false;
183     }
184 
185     /**
186      * Check the indentation of the right curly brace.
187      */
188     private void checkRightCurly() {
189         final DetailAST rcurly = getRightCurly();
190         final int rcurlyPos = expandedTabsColumnNo(rcurly);
191 
192         if (!curlyIndent().isAcceptable(rcurlyPos)
193                 && isOnStartOfLine(rcurly)) {
194             logError(rcurly, "rcurly", rcurlyPos, curlyIndent());
195         }
196     }
197 
198     /**
199      * Get the child element that is not a list of statements.
200      *
201      * @return the non-list child element
202      */
203     protected DetailAST getNonListChild() {
204         return getMainAst().findFirstToken(TokenTypes.RPAREN).getNextSibling();
205     }
206 
207     /**
208      * Check the indentation level of a child that is not a list of statements.
209      */
210     private void checkNonListChild() {
211         final DetailAST nonList = getNonListChild();
212         if (nonList != null) {
213             final IndentLevel expected = new IndentLevel(getIndent(), getBasicOffset());
214             checkExpressionSubtree(nonList, expected, false, false);
215 
216             final DetailAST nonListStartAst = getFirstAstNode(nonList);
217             if (nonList != nonListStartAst) {
218                 checkExpressionSubtree(nonListStartAst, expected, false, false);
219             }
220         }
221     }
222 
223     /**
224      * Get the child element representing the list of statements.
225      *
226      * @return the statement list child
227      */
228     protected DetailAST getListChild() {
229         return getMainAst().findFirstToken(TokenTypes.SLIST);
230     }
231 
232     /**
233      * Get the right parenthesis portion of the expression we are handling.
234      *
235      * @return the right parenthesis expression
236      */
237     private DetailAST getRightParen() {
238         return getMainAst().findFirstToken(TokenTypes.RPAREN);
239     }
240 
241     /**
242      * Get the left parenthesis portion of the expression we are handling.
243      *
244      * @return the left parenthesis expression
245      */
246     private DetailAST getLeftParen() {
247         return getMainAst().findFirstToken(TokenTypes.LPAREN);
248     }
249 
250     @Override
251     public void checkIndentation() {
252         checkTopLevelToken();
253         // separate to allow for eventual configuration
254         checkLeftParen(getLeftParen());
255         checkRightParen(getLeftParen(), getRightParen());
256         if (hasCurlies()) {
257             checkLeftCurly();
258             checkRightCurly();
259         }
260         final DetailAST listChild = getListChild();
261         if (listChild == null) {
262             checkNonListChild();
263         }
264         else {
265             // NOTE: switch statements usually don't have curlies
266             if (!hasCurlies() || !TokenUtil.areOnSameLine(getLeftCurly(), getRightCurly())) {
267                 checkChildren(listChild,
268                         getCheckedChildren(),
269                         getChildrenExpectedIndent(),
270                         true,
271                         canChildrenBeNested());
272             }
273         }
274     }
275 
276     /**
277      * Gets indentation level expected for children.
278      *
279      * @return indentation level expected for children
280      */
281     protected IndentLevel getChildrenExpectedIndent() {
282         IndentLevel indentLevel = new IndentLevel(getIndent(), getBasicOffset());
283         // if we have multileveled expected level then we should
284         // try to suggest single level to children using curlies'
285         // levels.
286         if (getIndent().isMultiLevel() && hasCurlies()) {
287             if (isOnStartOfLine(getLeftCurly())) {
288                 indentLevel = new IndentLevel(expandedTabsColumnNo(getLeftCurly())
289                         + getBasicOffset());
290             }
291             else if (isOnStartOfLine(getRightCurly())) {
292                 final IndentLevel level = new IndentLevel(curlyIndent(), getBasicOffset());
293                 indentLevel = IndentLevel.addAcceptable(level, level.getFirstIndentLevel()
294                         + getLineWrappingIndent());
295             }
296         }
297         if (hasCurlies() && isOnStartOfLine(getLeftCurly())) {
298             indentLevel = IndentLevel.addAcceptable(indentLevel,
299                     curlyIndent().getFirstIndentLevel() + getBasicOffset());
300         }
301         return indentLevel;
302     }
303 
304     @Override
305     public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) {
306         return getChildrenExpectedIndent();
307     }
308 
309     /**
310      * A shortcut for {@code IndentationCheck} property.
311      *
312      * @return value of lineWrappingIndentation property
313      *         of {@code IndentationCheck}
314      */
315     private int getLineWrappingIndent() {
316         return getIndentCheck().getLineWrappingIndentation();
317     }
318 
319 }