1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.blocks;
21
22 import java.util.Arrays;
23 import java.util.Locale;
24 import java.util.Optional;
25
26 import com.puppycrawl.tools.checkstyle.StatelessCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 @StatelessCheck
93 public class RightCurlyCheck extends AbstractCheck {
94
95
96
97
98
99 public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before";
100
101
102
103
104
105 public static final String MSG_KEY_LINE_ALONE = "line.alone";
106
107
108
109
110
111 public static final String MSG_KEY_LINE_SAME = "line.same";
112
113
114
115
116 private RightCurlyOption option = RightCurlyOption.SAME;
117
118
119
120
121
122
123
124
125 public void setOption(String optionStr) {
126 option = RightCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
127 }
128
129 @Override
130 public int[] getDefaultTokens() {
131 return new int[] {
132 TokenTypes.LITERAL_TRY,
133 TokenTypes.LITERAL_CATCH,
134 TokenTypes.LITERAL_FINALLY,
135 TokenTypes.LITERAL_IF,
136 TokenTypes.LITERAL_ELSE,
137 };
138 }
139
140 @Override
141 public int[] getAcceptableTokens() {
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 TokenTypes.CLASS_DEF,
149 TokenTypes.METHOD_DEF,
150 TokenTypes.CTOR_DEF,
151 TokenTypes.LITERAL_FOR,
152 TokenTypes.LITERAL_WHILE,
153 TokenTypes.LITERAL_DO,
154 TokenTypes.STATIC_INIT,
155 TokenTypes.INSTANCE_INIT,
156 TokenTypes.ANNOTATION_DEF,
157 TokenTypes.ENUM_DEF,
158 TokenTypes.INTERFACE_DEF,
159 TokenTypes.RECORD_DEF,
160 TokenTypes.COMPACT_CTOR_DEF,
161 TokenTypes.LITERAL_SWITCH,
162 TokenTypes.LITERAL_CASE,
163 };
164 }
165
166 @Override
167 public int[] getRequiredTokens() {
168 return CommonUtil.EMPTY_INT_ARRAY;
169 }
170
171 @Override
172 public void visitToken(DetailAST ast) {
173 final Details details = Details.getDetails(ast);
174 final DetailAST rcurly = details.rcurly;
175
176 if (rcurly != null) {
177 final String violation = validate(details);
178 if (!violation.isEmpty()) {
179 log(rcurly, violation, "}", rcurly.getColumnNo() + 1);
180 }
181 }
182 }
183
184
185
186
187
188
189
190
191 private String validate(Details details) {
192 String violation = "";
193 if (shouldHaveLineBreakBefore(option, details)) {
194 violation = MSG_KEY_LINE_BREAK_BEFORE;
195 }
196 else if (shouldBeOnSameLine(option, details)) {
197 violation = MSG_KEY_LINE_SAME;
198 }
199 else if (shouldBeAloneOnLine(option, details, getLine(details.rcurly.getLineNo() - 1))) {
200 violation = MSG_KEY_LINE_ALONE;
201 }
202 return violation;
203 }
204
205
206
207
208
209
210
211
212 private static boolean shouldHaveLineBreakBefore(RightCurlyOption bracePolicy,
213 Details details) {
214 return bracePolicy == RightCurlyOption.SAME
215 && !hasLineBreakBefore(details.rcurly)
216 && !TokenUtil.areOnSameLine(details.lcurly, details.rcurly);
217 }
218
219
220
221
222
223
224
225
226 private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) {
227 return bracePolicy == RightCurlyOption.SAME
228 && !details.shouldCheckLastRcurly
229 && !TokenUtil.areOnSameLine(details.rcurly, details.nextToken);
230 }
231
232
233
234
235
236
237
238
239
240 private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy,
241 Details details,
242 String targetSrcLine) {
243 return bracePolicy == RightCurlyOption.ALONE
244 && shouldBeAloneOnLineWithAloneOption(details, targetSrcLine)
245 || (bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE
246 || details.shouldCheckLastRcurly)
247 && shouldBeAloneOnLineWithNotAloneOption(details, targetSrcLine);
248 }
249
250
251
252
253
254
255
256
257 private static boolean shouldBeAloneOnLineWithAloneOption(Details details,
258 String targetSrcLine) {
259 return !isAloneOnLine(details, targetSrcLine);
260 }
261
262
263
264
265
266
267
268
269
270 private static boolean shouldBeAloneOnLineWithNotAloneOption(Details details,
271 String targetSrcLine) {
272 return shouldBeAloneOnLineWithAloneOption(details, targetSrcLine)
273 && !isBlockAloneOnSingleLine(details);
274 }
275
276
277
278
279
280
281
282
283 private static boolean isAloneOnLine(Details details, String targetSrcLine) {
284 final DetailAST rcurly = details.rcurly;
285 final DetailAST nextToken = details.nextToken;
286 return (nextToken == null || !TokenUtil.areOnSameLine(rcurly, nextToken)
287 || skipDoubleBraceInstInit(details))
288 && CommonUtil.hasWhitespaceBefore(details.rcurly.getColumnNo(),
289 targetSrcLine);
290 }
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310 private static boolean skipDoubleBraceInstInit(Details details) {
311 boolean skipDoubleBraceInstInit = false;
312 final DetailAST tokenAfterNextToken = Details.getNextToken(details.nextToken);
313 if (tokenAfterNextToken != null) {
314 final DetailAST rcurly = details.rcurly;
315 skipDoubleBraceInstInit = rcurly.getParent().getParent()
316 .getType() == TokenTypes.INSTANCE_INIT
317 && details.nextToken.getType() == TokenTypes.RCURLY
318 && !TokenUtil.areOnSameLine(rcurly, Details.getNextToken(tokenAfterNextToken));
319 }
320 return skipDoubleBraceInstInit;
321 }
322
323
324
325
326
327
328
329 private static boolean isBlockAloneOnSingleLine(Details details) {
330 DetailAST nextToken = details.nextToken;
331
332 while (nextToken != null && nextToken.getType() == TokenTypes.LITERAL_ELSE) {
333 nextToken = Details.getNextToken(nextToken);
334 }
335
336
337 final int[] tokensWithBlockSibling = {
338 TokenTypes.DO_WHILE,
339 TokenTypes.LITERAL_FINALLY,
340 TokenTypes.LITERAL_CATCH,
341 };
342
343 if (TokenUtil.isOfType(nextToken, tokensWithBlockSibling)) {
344 final DetailAST parent = nextToken.getParent();
345 nextToken = Details.getNextToken(parent);
346 }
347
348 return TokenUtil.areOnSameLine(details.lcurly, details.rcurly)
349 && (nextToken == null || !TokenUtil.areOnSameLine(details.rcurly, nextToken)
350 || isRightcurlyFollowedBySemicolon(details));
351 }
352
353
354
355
356
357
358
359 private static boolean isRightcurlyFollowedBySemicolon(Details details) {
360 return details.nextToken.getType() == TokenTypes.SEMI;
361 }
362
363
364
365
366
367
368
369 private static boolean hasLineBreakBefore(DetailAST rightCurly) {
370 DetailAST previousToken = rightCurly.getPreviousSibling();
371 if (previousToken == null) {
372 previousToken = rightCurly.getParent();
373 }
374 return !TokenUtil.areOnSameLine(rightCurly, previousToken);
375 }
376
377
378
379
380 private static final class Details {
381
382
383
384
385 private static final int[] TOKENS_WITH_NO_CHILD_SLIST = {
386 TokenTypes.CLASS_DEF,
387 TokenTypes.ENUM_DEF,
388 TokenTypes.ANNOTATION_DEF,
389 TokenTypes.INTERFACE_DEF,
390 TokenTypes.RECORD_DEF,
391 };
392
393
394 private final DetailAST rcurly;
395
396 private final DetailAST lcurly;
397
398 private final DetailAST nextToken;
399
400 private final boolean shouldCheckLastRcurly;
401
402
403
404
405
406
407
408
409
410 private Details(DetailAST lcurly, DetailAST rcurly,
411 DetailAST nextToken, boolean shouldCheckLastRcurly) {
412 this.lcurly = lcurly;
413 this.rcurly = rcurly;
414 this.nextToken = nextToken;
415 this.shouldCheckLastRcurly = shouldCheckLastRcurly;
416 }
417
418
419
420
421
422
423
424 private static Details getDetails(DetailAST ast) {
425 final Details details;
426 switch (ast.getType()) {
427 case TokenTypes.LITERAL_TRY:
428 case TokenTypes.LITERAL_CATCH:
429 details = getDetailsForTryCatch(ast);
430 break;
431 case TokenTypes.LITERAL_IF:
432 details = getDetailsForIf(ast);
433 break;
434 case TokenTypes.LITERAL_DO:
435 details = getDetailsForDoLoops(ast);
436 break;
437 case TokenTypes.LITERAL_SWITCH:
438 details = getDetailsForSwitch(ast);
439 break;
440 case TokenTypes.LITERAL_CASE:
441 details = getDetailsForCase(ast);
442 break;
443 default:
444 details = getDetailsForOthers(ast);
445 break;
446 }
447 return details;
448 }
449
450
451
452
453
454
455
456 private static Details getDetailsForSwitch(DetailAST switchNode) {
457 final DetailAST lcurly = switchNode.findFirstToken(TokenTypes.LCURLY);
458 final DetailAST rcurly;
459 DetailAST nextToken = null;
460
461 if (isSwitchExpression(switchNode)) {
462 rcurly = null;
463 }
464 else {
465 rcurly = switchNode.getLastChild();
466 nextToken = getNextToken(switchNode);
467 }
468 return new Details(lcurly, rcurly, nextToken, true);
469 }
470
471
472
473
474
475
476
477 private static Details getDetailsForCase(DetailAST caseNode) {
478 final DetailAST caseParent = caseNode.getParent();
479 final int parentType = caseParent.getType();
480 final Optional<DetailAST> lcurly;
481 final DetailAST statementList;
482
483 if (parentType == TokenTypes.SWITCH_RULE) {
484 statementList = caseParent.findFirstToken(TokenTypes.SLIST);
485 lcurly = Optional.ofNullable(statementList);
486 }
487 else {
488 statementList = caseNode.getNextSibling();
489 lcurly = Optional.ofNullable(statementList)
490 .map(DetailAST::getFirstChild)
491 .filter(node -> node.getType() == TokenTypes.SLIST);
492 }
493 final DetailAST rcurly = lcurly.map(DetailAST::getLastChild)
494 .filter(child -> !isSwitchExpression(caseParent))
495 .orElse(null);
496 final Optional<DetailAST> nextToken =
497 Optional.ofNullable(lcurly.map(DetailAST::getNextSibling)
498 .orElseGet(() -> getNextToken(caseParent)));
499
500 return new Details(lcurly.orElse(null), rcurly, nextToken.orElse(null), true);
501 }
502
503
504
505
506
507
508
509 private static boolean isSwitchExpression(DetailAST switchNode) {
510 DetailAST currentNode = switchNode;
511 boolean ans = false;
512
513 while (currentNode != null) {
514 if (currentNode.getType() == TokenTypes.EXPR) {
515 ans = true;
516 }
517 currentNode = currentNode.getParent();
518 }
519 return ans;
520 }
521
522
523
524
525
526
527
528 private static Details getDetailsForTryCatch(DetailAST ast) {
529 final DetailAST lcurly;
530 DetailAST nextToken;
531 final int tokenType = ast.getType();
532 if (tokenType == TokenTypes.LITERAL_TRY) {
533 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) {
534 lcurly = ast.getFirstChild().getNextSibling();
535 }
536 else {
537 lcurly = ast.getFirstChild();
538 }
539 nextToken = lcurly.getNextSibling();
540 }
541 else {
542 nextToken = ast.getNextSibling();
543 lcurly = ast.getLastChild();
544 }
545
546 final boolean shouldCheckLastRcurly;
547 if (nextToken == null) {
548 shouldCheckLastRcurly = true;
549 nextToken = getNextToken(ast);
550 }
551 else {
552 shouldCheckLastRcurly = false;
553 }
554
555 final DetailAST rcurly = lcurly.getLastChild();
556 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
557 }
558
559
560
561
562
563
564
565 private static Details getDetailsForIf(DetailAST ast) {
566 final boolean shouldCheckLastRcurly;
567 final DetailAST lcurly;
568 DetailAST nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
569
570 if (nextToken == null) {
571 shouldCheckLastRcurly = true;
572 nextToken = getNextToken(ast);
573 lcurly = ast.getLastChild();
574 }
575 else {
576 shouldCheckLastRcurly = false;
577 lcurly = nextToken.getPreviousSibling();
578 }
579
580 DetailAST rcurly = null;
581 if (lcurly.getType() == TokenTypes.SLIST) {
582 rcurly = lcurly.getLastChild();
583 }
584 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
585 }
586
587
588
589
590
591
592
593
594 private static Details getDetailsForOthers(DetailAST ast) {
595 DetailAST rcurly = null;
596 final DetailAST lcurly;
597 final int tokenType = ast.getType();
598 if (isTokenWithNoChildSlist(tokenType)) {
599 final DetailAST child = ast.getLastChild();
600 lcurly = child;
601 rcurly = child.getLastChild();
602 }
603 else {
604 lcurly = ast.findFirstToken(TokenTypes.SLIST);
605 if (lcurly != null) {
606
607 rcurly = lcurly.getLastChild();
608 }
609 }
610 return new Details(lcurly, rcurly, getNextToken(ast), true);
611 }
612
613
614
615
616
617
618
619
620 private static boolean isTokenWithNoChildSlist(int tokenType) {
621 return Arrays.stream(TOKENS_WITH_NO_CHILD_SLIST).anyMatch(token -> token == tokenType);
622 }
623
624
625
626
627
628
629
630 private static Details getDetailsForDoLoops(DetailAST ast) {
631 final DetailAST lcurly = ast.findFirstToken(TokenTypes.SLIST);
632 final DetailAST nextToken = ast.findFirstToken(TokenTypes.DO_WHILE);
633 DetailAST rcurly = null;
634 if (lcurly != null) {
635 rcurly = lcurly.getLastChild();
636 }
637 return new Details(lcurly, rcurly, nextToken, false);
638 }
639
640
641
642
643
644
645
646 private static DetailAST getNextToken(DetailAST ast) {
647 DetailAST next = null;
648 DetailAST parent = ast;
649 while (next == null && parent != null) {
650 next = parent.getNextSibling();
651 parent = parent.getParent();
652 }
653 return next;
654 }
655 }
656 }