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