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.blocks;
21  
22  import java.util.Locale;
23  
24  import com.puppycrawl.tools.checkstyle.StatelessCheck;
25  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26  import com.puppycrawl.tools.checkstyle.api.DetailAST;
27  import com.puppycrawl.tools.checkstyle.api.Scope;
28  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29  import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
30  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
31  import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
32  
33  /**
34   * <p>
35   * Checks the placement of right curly braces.
36   * The policy to verify is specified using the {@link RightCurlyOption} class
37   * and defaults to {@link RightCurlyOption#SAME}.
38   * </p>
39   * <p> By default the check will check the following tokens:
40   *  {@link TokenTypes#LITERAL_TRY LITERAL_TRY},
41   *  {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH},
42   *  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},
43   *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
44   *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}.
45   * Other acceptable tokens are:
46   *  {@link TokenTypes#CLASS_DEF CLASS_DEF},
47   *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
48   *  {@link TokenTypes#CTOR_DEF CTOR_DEF}.
49   *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR}.
50   *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}.
51   *  {@link TokenTypes#LITERAL_DO LITERAL_DO}.
52   *  {@link TokenTypes#STATIC_INIT STATIC_INIT}.
53   *  {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}.
54   *  {@link TokenTypes#LAMBDA LAMBDA}.
55   * </p>
56   * <p>
57   * <b>shouldStartLine</b> - does the check need to check
58   * if right curly starts line. Default value is <b>true</b>
59   * </p>
60   * <p>
61   * An example of how to configure the check is:
62   * </p>
63   * <pre>
64   * &lt;module name="RightCurly"/&gt;
65   * </pre>
66   * <p>
67   * An example of how to configure the check with policy
68   * {@link RightCurlyOption#ALONE} for {@code else} and
69   * {@code {@link TokenTypes#METHOD_DEF METHOD_DEF}}tokens is:
70   * </p>
71   * <pre>
72   * &lt;module name="RightCurly"&gt;
73   *     &lt;property name="tokens" value="LITERAL_ELSE"/&gt;
74   *     &lt;property name="option" value="alone"/&gt;
75   * &lt;/module&gt;
76   * </pre>
77   *
78   * @author Oliver Burn
79   * @author lkuehne
80   * @author o_sukhodolsky
81   * @author maxvetrenko
82   * @author Andrei Selkin
83   * @author <a href="mailto:piotr.listkiewicz@gmail.com">liscju</a>
84   */
85  @StatelessCheck
86  public class RightCurlyCheck extends AbstractCheck {
87      /**
88       * A key is pointing to the warning message text in "messages.properties"
89       * file.
90       */
91      public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before";
92  
93      /**
94       * A key is pointing to the warning message text in "messages.properties"
95       * file.
96       */
97      public static final String MSG_KEY_LINE_ALONE = "line.alone";
98  
99      /**
100      * A key is pointing to the warning message text in "messages.properties"
101      * file.
102      */
103     public static final String MSG_KEY_LINE_SAME = "line.same";
104 
105     /**
106      * A key is pointing to the warning message text in "messages.properties"
107      * file.
108      */
109     public static final String MSG_KEY_LINE_NEW = "line.new";
110 
111     /** Do we need to check if right curly starts line. */
112     private boolean shouldStartLine = true;
113 
114     /** The policy to enforce. */
115     private RightCurlyOption option = RightCurlyOption.SAME;
116 
117     /**
118      * Sets the option to enforce.
119      * @param optionStr string to decode option from
120      * @throws IllegalArgumentException if unable to decode
121      */
122     public void setOption(String optionStr) {
123         try {
124             option = RightCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
125         }
126         catch (IllegalArgumentException iae) {
127             throw new IllegalArgumentException("unable to parse " + optionStr, iae);
128         }
129     }
130 
131     /**
132      * Does the check need to check if right curly starts line.
133      * @param flag new value of this property.
134      */
135     public void setShouldStartLine(boolean flag) {
136         shouldStartLine = flag;
137     }
138 
139     @Override
140     public int[] getDefaultTokens() {
141         return new int[] {
142             TokenTypes.LITERAL_TRY,
143             TokenTypes.LITERAL_CATCH,
144             TokenTypes.LITERAL_FINALLY,
145             TokenTypes.LITERAL_IF,
146             TokenTypes.LITERAL_ELSE,
147         };
148     }
149 
150     @Override
151     public int[] getAcceptableTokens() {
152         return new int[] {
153             TokenTypes.LITERAL_TRY,
154             TokenTypes.LITERAL_CATCH,
155             TokenTypes.LITERAL_FINALLY,
156             TokenTypes.LITERAL_IF,
157             TokenTypes.LITERAL_ELSE,
158             TokenTypes.CLASS_DEF,
159             TokenTypes.METHOD_DEF,
160             TokenTypes.CTOR_DEF,
161             TokenTypes.LITERAL_FOR,
162             TokenTypes.LITERAL_WHILE,
163             TokenTypes.LITERAL_DO,
164             TokenTypes.STATIC_INIT,
165             TokenTypes.INSTANCE_INIT,
166             TokenTypes.LAMBDA,
167         };
168     }
169 
170     @Override
171     public int[] getRequiredTokens() {
172         return CommonUtils.EMPTY_INT_ARRAY;
173     }
174 
175     @Override
176     public void visitToken(DetailAST ast) {
177         final Details details = Details.getDetails(ast);
178         final DetailAST rcurly = details.rcurly;
179 
180         if (rcurly != null) {
181             final String violation = validate(details);
182             if (!violation.isEmpty()) {
183                 log(rcurly, violation, "}", rcurly.getColumnNo() + 1);
184             }
185         }
186     }
187 
188     /**
189      * Does general validation.
190      * @param details for validation.
191      * @return violation message or empty string
192      *     if there was not violation during validation.
193      */
194     private String validate(Details details) {
195         String violation = "";
196         if (shouldHaveLineBreakBefore(option, details)) {
197             violation = MSG_KEY_LINE_BREAK_BEFORE;
198         }
199         else if (shouldBeOnSameLine(option, details)) {
200             violation = MSG_KEY_LINE_SAME;
201         }
202         else if (shouldBeAloneOnLine(option, details)) {
203             violation = MSG_KEY_LINE_ALONE;
204         }
205         else if (shouldStartLine) {
206             final String targetSourceLine = getLines()[details.rcurly.getLineNo() - 1];
207             if (!isOnStartOfLine(details, targetSourceLine)) {
208                 violation = MSG_KEY_LINE_NEW;
209             }
210         }
211         return violation;
212     }
213 
214     /**
215      * Checks whether a right curly should have a line break before.
216      * @param bracePolicy option for placing the right curly brace.
217      * @param details details for validation.
218      * @return true if a right curly should have a line break before.
219      */
220     private static boolean shouldHaveLineBreakBefore(RightCurlyOption bracePolicy,
221                                                      Details details) {
222         return bracePolicy == RightCurlyOption.SAME
223                 && !hasLineBreakBefore(details.rcurly)
224                 && details.lcurly.getLineNo() != details.rcurly.getLineNo();
225     }
226 
227     /**
228      * Checks that a right curly should be on the same line as the next statement.
229      * @param bracePolicy option for placing the right curly brace
230      * @param details Details for validation
231      * @return true if a right curly should be alone on a line.
232      */
233     private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) {
234         return bracePolicy == RightCurlyOption.SAME
235                 && !details.shouldCheckLastRcurly
236                 && details.rcurly.getLineNo() != details.nextToken.getLineNo();
237     }
238 
239     /**
240      * Checks that a right curly should be alone on a line.
241      * @param bracePolicy option for placing the right curly brace
242      * @param details Details for validation
243      * @return true if a right curly should be alone on a line.
244      */
245     private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy, Details details) {
246         return bracePolicy == RightCurlyOption.ALONE
247                     && shouldBeAloneOnLineWithAloneOption(details)
248                 || bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE
249                     && shouldBeAloneOnLineWithAloneOrSinglelineOption(details)
250                 || details.shouldCheckLastRcurly
251                     && details.rcurly.getLineNo() == details.nextToken.getLineNo();
252     }
253 
254     /**
255      * Whether right curly should be alone on line when ALONE option is used.
256      * @param details details for validation.
257      * @return true, if right curly should be alone on line when ALONE option is used.
258      */
259     private static boolean shouldBeAloneOnLineWithAloneOption(Details details) {
260         return !isAloneOnLine(details)
261                 && !isEmptyBody(details.lcurly);
262     }
263 
264     /**
265      * Whether right curly should be alone on line when ALONE_OR_SINGLELINE option is used.
266      * @param details details for validation.
267      * @return true, if right curly should be alone on line
268      *         when ALONE_OR_SINGLELINE option is used.
269      */
270     private static boolean shouldBeAloneOnLineWithAloneOrSinglelineOption(Details details) {
271         return !isAloneOnLine(details)
272                 && !isSingleLineBlock(details)
273                 && !isAnonInnerClassInit(details.lcurly)
274                 && !isEmptyBody(details.lcurly);
275     }
276 
277     /**
278      * Whether right curly brace starts target source line.
279      * @param details Details of right curly brace for validation
280      * @param targetSourceLine source line to check
281      * @return true if right curly brace starts target source line.
282      */
283     private static boolean isOnStartOfLine(Details details, String targetSourceLine) {
284         return CommonUtils.hasWhitespaceBefore(details.rcurly.getColumnNo(), targetSourceLine)
285                 || details.lcurly.getLineNo() == details.rcurly.getLineNo();
286     }
287 
288     /**
289      * Checks whether right curly is alone on a line.
290      * @param details for validation.
291      * @return true if right curly is alone on a line.
292      */
293     private static boolean isAloneOnLine(Details details) {
294         final DetailAST rcurly = details.rcurly;
295         final DetailAST lcurly = details.lcurly;
296         final DetailAST nextToken = details.nextToken;
297         return rcurly.getLineNo() != lcurly.getLineNo()
298             && rcurly.getLineNo() != nextToken.getLineNo();
299     }
300 
301     /**
302      * Checks whether block has a single-line format.
303      * @param details for validation.
304      * @return true if block has single-line format.
305      */
306     private static boolean isSingleLineBlock(Details details) {
307         final DetailAST rcurly = details.rcurly;
308         final DetailAST lcurly = details.lcurly;
309         final DetailAST nextToken = details.nextToken;
310         return rcurly.getLineNo() == lcurly.getLineNo()
311             && rcurly.getLineNo() != nextToken.getLineNo();
312     }
313 
314     /**
315      * Checks whether lcurly is in anonymous inner class initialization.
316      * @param lcurly left curly token.
317      * @return true if lcurly begins anonymous inner class initialization.
318      */
319     private static boolean isAnonInnerClassInit(DetailAST lcurly) {
320         final Scope surroundingScope = ScopeUtils.getSurroundingScope(lcurly);
321         return surroundingScope.ordinal() == Scope.ANONINNER.ordinal();
322     }
323 
324     /**
325      * Checks if definition body is empty.
326      * @param lcurly left curly.
327      * @return true if definition body is empty.
328      */
329     private static boolean isEmptyBody(DetailAST lcurly) {
330         boolean result = false;
331         if (lcurly.getParent().getType() == TokenTypes.OBJBLOCK) {
332             if (lcurly.getNextSibling().getType() == TokenTypes.RCURLY) {
333                 result = true;
334             }
335         }
336         else if (lcurly.getFirstChild().getType() == TokenTypes.RCURLY) {
337             result = true;
338         }
339         return result;
340     }
341 
342     /**
343      * Checks if right curly has line break before.
344      * @param rightCurly right curly token.
345      * @return true, if right curly has line break before.
346      */
347     private static boolean hasLineBreakBefore(DetailAST rightCurly) {
348         final DetailAST previousToken = rightCurly.getPreviousSibling();
349         return previousToken == null
350                 || rightCurly.getLineNo() != previousToken.getLineNo();
351     }
352 
353     /**
354      * Structure that contains all details for validation.
355      */
356     private static final class Details {
357 
358         /** Right curly. */
359         private final DetailAST rcurly;
360         /** Left curly. */
361         private final DetailAST lcurly;
362         /** Next token. */
363         private final DetailAST nextToken;
364         /** Should check last right curly. */
365         private final boolean shouldCheckLastRcurly;
366 
367         /**
368          * Constructor.
369          * @param lcurly the lcurly of the token whose details are being collected
370          * @param rcurly the rcurly of the token whose details are being collected
371          * @param nextToken the token after the token whose details are being collected
372          * @param shouldCheckLastRcurly boolean value to determine if to check last rcurly
373          */
374         private Details(DetailAST lcurly, DetailAST rcurly,
375                         DetailAST nextToken, boolean shouldCheckLastRcurly) {
376             this.lcurly = lcurly;
377             this.rcurly = rcurly;
378             this.nextToken = nextToken;
379             this.shouldCheckLastRcurly = shouldCheckLastRcurly;
380         }
381 
382         /**
383          * Collects validation Details.
384          * @param ast a {@code DetailAST} value
385          * @return object containing all details to make a validation
386          */
387         private static Details getDetails(DetailAST ast) {
388             final Details details;
389             switch (ast.getType()) {
390                 case TokenTypes.LITERAL_TRY:
391                 case TokenTypes.LITERAL_CATCH:
392                 case TokenTypes.LITERAL_FINALLY:
393                     details = getDetailsForTryCatchFinally(ast);
394                     break;
395                 case TokenTypes.LITERAL_IF:
396                 case TokenTypes.LITERAL_ELSE:
397                     details = getDetailsForIfElse(ast);
398                     break;
399                 case TokenTypes.LITERAL_DO:
400                 case TokenTypes.LITERAL_WHILE:
401                 case TokenTypes.LITERAL_FOR:
402                     details = getDetailsForLoops(ast);
403                     break;
404                 case TokenTypes.LAMBDA:
405                     details = getDetailsForLambda(ast);
406                     break;
407                 default:
408                     details = getDetailsForOthers(ast);
409                     break;
410             }
411             return details;
412         }
413 
414         /**
415          * Collects validation details for LITERAL_TRY, LITERAL_CATCH, and LITERAL_FINALLY.
416          * @param ast a {@code DetailAST} value
417          * @return object containing all details to make a validation
418          */
419         private static Details getDetailsForTryCatchFinally(DetailAST ast) {
420             boolean shouldCheckLastRcurly = false;
421             final DetailAST rcurly;
422             final DetailAST lcurly;
423             DetailAST nextToken;
424             final int tokenType = ast.getType();
425             if (tokenType == TokenTypes.LITERAL_TRY) {
426                 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) {
427                     lcurly = ast.getFirstChild().getNextSibling();
428                 }
429                 else {
430                     lcurly = ast.getFirstChild();
431                 }
432                 nextToken = lcurly.getNextSibling();
433                 rcurly = lcurly.getLastChild();
434 
435                 if (nextToken == null) {
436                     shouldCheckLastRcurly = true;
437                     nextToken = getNextToken(ast);
438                 }
439             }
440             else if (tokenType == TokenTypes.LITERAL_CATCH) {
441                 nextToken = ast.getNextSibling();
442                 lcurly = ast.getLastChild();
443                 rcurly = lcurly.getLastChild();
444                 if (nextToken == null) {
445                     shouldCheckLastRcurly = true;
446                     nextToken = getNextToken(ast);
447                 }
448 
449             }
450             else {
451                 shouldCheckLastRcurly = true;
452                 nextToken = getNextToken(ast);
453                 lcurly = ast.getFirstChild();
454                 rcurly = lcurly.getLastChild();
455             }
456             return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
457         }
458 
459         /**
460          * Collects validation details for LITERAL_IF and LITERAL_ELSE.
461          * @param ast a {@code DetailAST} value
462          * @return object containing all details to make a validation
463          */
464         private static Details getDetailsForIfElse(DetailAST ast) {
465             boolean shouldCheckLastRcurly = false;
466             DetailAST rcurly = null;
467             final DetailAST lcurly;
468             DetailAST nextToken;
469             final int tokenType = ast.getType();
470             if (tokenType == TokenTypes.LITERAL_IF) {
471                 nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
472                 if (nextToken == null) {
473                     shouldCheckLastRcurly = true;
474                     nextToken = getNextToken(ast);
475                     lcurly = ast.getLastChild();
476                 }
477                 else {
478                     lcurly = nextToken.getPreviousSibling();
479                 }
480                 if (lcurly.getType() == TokenTypes.SLIST) {
481                     rcurly = lcurly.getLastChild();
482                 }
483 
484             }
485             else {
486                 shouldCheckLastRcurly = true;
487                 nextToken = getNextToken(ast);
488                 lcurly = ast.getFirstChild();
489                 if (lcurly.getType() == TokenTypes.SLIST) {
490                     rcurly = lcurly.getLastChild();
491                 }
492             }
493             return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
494         }
495 
496         /**
497          * Collects validation details for CLASS_DEF, METHOD DEF, CTOR_DEF, STATIC_INIT, and
498          * INSTANCE_INIT.
499          * @param ast a {@code DetailAST} value
500          * @return an object containing all details to make a validation
501          */
502         private static Details getDetailsForOthers(DetailAST ast) {
503             DetailAST rcurly = null;
504             final DetailAST lcurly;
505             final DetailAST nextToken;
506             final int tokenType = ast.getType();
507             if (tokenType == TokenTypes.CLASS_DEF) {
508                 final DetailAST child = ast.getLastChild();
509                 lcurly = child.getFirstChild();
510                 rcurly = child.getLastChild();
511                 nextToken = ast;
512             }
513             else if (tokenType == TokenTypes.METHOD_DEF) {
514                 lcurly = ast.findFirstToken(TokenTypes.SLIST);
515                 if (lcurly != null) {
516                     // SLIST could be absent if method is abstract
517                     rcurly = lcurly.getLastChild();
518                 }
519                 nextToken = getNextToken(ast);
520             }
521             else {
522                 lcurly = ast.findFirstToken(TokenTypes.SLIST);
523                 rcurly = lcurly.getLastChild();
524                 nextToken = getNextToken(ast);
525             }
526             return new Details(lcurly, rcurly, nextToken, false);
527         }
528 
529         /**
530          * Collects validation details for loops' tokens.
531          * @param ast a {@code DetailAST} value
532          * @return an object containing all details to make a validation
533          */
534         private static Details getDetailsForLoops(DetailAST ast) {
535             DetailAST rcurly = null;
536             final DetailAST lcurly;
537             final DetailAST nextToken;
538             final int tokenType = ast.getType();
539             if (tokenType == TokenTypes.LITERAL_DO) {
540                 nextToken = ast.findFirstToken(TokenTypes.DO_WHILE);
541                 lcurly = ast.findFirstToken(TokenTypes.SLIST);
542                 if (lcurly != null) {
543                     rcurly = lcurly.getLastChild();
544                 }
545             }
546             else {
547                 lcurly = ast.findFirstToken(TokenTypes.SLIST);
548                 if (lcurly != null) {
549                     // SLIST could be absent in code like "while(true);"
550                     rcurly = lcurly.getLastChild();
551                 }
552                 nextToken = getNextToken(ast);
553             }
554             return new Details(lcurly, rcurly, nextToken, false);
555         }
556 
557         /**
558          * Collects validation details for Lambdas.
559          * @param ast a {@code DetailAST} value
560          * @return an object containing all details to make a validation
561          */
562         private static Details getDetailsForLambda(DetailAST ast) {
563             final DetailAST lcurly = ast.findFirstToken(TokenTypes.SLIST);
564             boolean shouldCheckLastRcurly = false;
565             DetailAST nextToken = getNextToken(ast);
566             if (nextToken.getType() != TokenTypes.RPAREN
567                     && nextToken.getType() != TokenTypes.COMMA) {
568                 shouldCheckLastRcurly = true;
569                 nextToken = getNextToken(nextToken);
570             }
571             DetailAST rcurly = null;
572             if (lcurly != null) {
573                 rcurly = lcurly.getLastChild();
574             }
575             return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
576         }
577 
578         /**
579          * Finds next token after the given one.
580          * @param ast the given node.
581          * @return the token which represents next lexical item.
582          */
583         private static DetailAST getNextToken(DetailAST ast) {
584             DetailAST next = null;
585             DetailAST parent = ast;
586             while (next == null) {
587                 next = parent.getNextSibling();
588                 parent = parent.getParent();
589             }
590             return CheckUtils.getFirstNode(next);
591         }
592     }
593 }