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