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