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