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