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.javadoc;
21
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.ListIterator;
28 import java.util.Set;
29 import java.util.regex.MatchResult;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32
33 import com.puppycrawl.tools.checkstyle.StatelessCheck;
34 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
35 import com.puppycrawl.tools.checkstyle.api.DetailAST;
36 import com.puppycrawl.tools.checkstyle.api.FileContents;
37 import com.puppycrawl.tools.checkstyle.api.FullIdent;
38 import com.puppycrawl.tools.checkstyle.api.TextBlock;
39 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
40 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
41 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
42 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
43 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
44 import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil;
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188 @StatelessCheck
189 public class JavadocMethodCheck extends AbstractCheck {
190
191
192
193
194
195 public static final String MSG_CLASS_INFO = "javadoc.classInfo";
196
197
198
199
200
201 public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
202
203
204
205
206
207 public static final String MSG_INVALID_INHERIT_DOC = "javadoc.invalidInheritDoc";
208
209
210
211
212
213 public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
214
215
216
217
218
219 public static final String MSG_EXPECTED_TAG = "javadoc.expectedTag";
220
221
222
223
224
225 public static final String MSG_RETURN_EXPECTED = "javadoc.return.expected";
226
227
228
229
230
231 public static final String MSG_DUPLICATE_TAG = "javadoc.duplicateTag";
232
233
234 private static final String ELEMENT_START = "<";
235
236
237 private static final String ELEMENT_END = ">";
238
239
240 private static final Pattern MATCH_JAVADOC_ARG = CommonUtil.createPattern(
241 "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+(\\S+)\\s+\\S*");
242
243 private static final Pattern MATCH_JAVADOC_ARG_MISSING_DESCRIPTION =
244 CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+"
245 + "(\\S[^*]*)(?:(\\s+|\\*\\/))?");
246
247
248 private static final Pattern MATCH_JAVADOC_MULTILINE_CONT =
249 CommonUtil.createPattern("(\\*\\/|@|[^\\s\\*])");
250
251
252 private static final String END_JAVADOC = "*/";
253
254 private static final String NEXT_TAG = "@";
255
256
257 private static final Pattern MATCH_JAVADOC_NOARG =
258 CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s+\\S");
259
260 private static final Pattern MATCH_JAVADOC_NOARG_MULTILINE_START =
261 CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s*$");
262
263 private static final Pattern MATCH_JAVADOC_NOARG_CURLY =
264 CommonUtil.createPattern("\\{\\s*@(inheritDoc)\\s*\\}");
265
266
267 private AccessModifierOption[] accessModifiers = {
268 AccessModifierOption.PUBLIC,
269 AccessModifierOption.PROTECTED,
270 AccessModifierOption.PACKAGE,
271 AccessModifierOption.PRIVATE,
272 };
273
274
275
276
277 private boolean validateThrows;
278
279
280
281
282
283 private boolean allowMissingParamTags;
284
285
286
287
288
289 private boolean allowMissingReturnTag;
290
291
292 private Set<String> allowedAnnotations = Set.of("Override");
293
294
295
296
297
298
299
300 public void setValidateThrows(boolean value) {
301 validateThrows = value;
302 }
303
304
305
306
307
308
309
310 public void setAllowedAnnotations(String... userAnnotations) {
311 allowedAnnotations = Set.of(userAnnotations);
312 }
313
314
315
316
317
318
319
320 public void setAccessModifiers(AccessModifierOption... accessModifiers) {
321 this.accessModifiers =
322 UnmodifiableCollectionUtil.copyOfArray(accessModifiers, accessModifiers.length);
323 }
324
325
326
327
328
329
330
331
332 public void setAllowMissingParamTags(boolean flag) {
333 allowMissingParamTags = flag;
334 }
335
336
337
338
339
340
341
342
343 public void setAllowMissingReturnTag(boolean flag) {
344 allowMissingReturnTag = flag;
345 }
346
347 @Override
348 public final int[] getRequiredTokens() {
349 return CommonUtil.EMPTY_INT_ARRAY;
350 }
351
352 @Override
353 public int[] getDefaultTokens() {
354 return getAcceptableTokens();
355 }
356
357 @Override
358 public int[] getAcceptableTokens() {
359 return new int[] {
360 TokenTypes.METHOD_DEF,
361 TokenTypes.CTOR_DEF,
362 TokenTypes.ANNOTATION_FIELD_DEF,
363 TokenTypes.COMPACT_CTOR_DEF,
364 };
365 }
366
367 @Override
368 public final void visitToken(DetailAST ast) {
369 processAST(ast);
370 }
371
372
373
374
375
376
377
378
379 @SuppressWarnings("deprecation")
380 private void processAST(DetailAST ast) {
381 if (shouldCheck(ast)) {
382 final FileContents contents = getFileContents();
383 final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo());
384
385 if (textBlock != null) {
386 checkComment(ast, textBlock);
387 }
388 }
389 }
390
391
392
393
394
395
396
397 private boolean shouldCheck(final DetailAST ast) {
398 final AccessModifierOption surroundingAccessModifier = CheckUtil
399 .getSurroundingAccessModifier(ast);
400 final AccessModifierOption accessModifier = CheckUtil
401 .getAccessModifierFromModifiersToken(ast);
402 return Arrays.stream(accessModifiers)
403 .anyMatch(modifier -> modifier == surroundingAccessModifier)
404 && Arrays.stream(accessModifiers).anyMatch(modifier -> modifier == accessModifier);
405 }
406
407
408
409
410
411
412
413 private void checkComment(DetailAST ast, TextBlock comment) {
414 final List<JavadocTag> tags = getMethodTags(comment);
415
416 if (!hasShortCircuitTag(ast, tags)) {
417 if (ast.getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
418 checkReturnTag(tags, ast.getLineNo(), true);
419 }
420 else {
421 final Iterator<JavadocTag> it = tags.iterator();
422
423 boolean hasInheritDocTag = false;
424 while (!hasInheritDocTag && it.hasNext()) {
425 hasInheritDocTag = it.next().isInheritDocTag();
426 }
427 final boolean reportExpectedTags = !hasInheritDocTag
428 && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations);
429
430
431 if (ast.getType() == TokenTypes.COMPACT_CTOR_DEF) {
432 checkRecordParamTags(tags, ast, reportExpectedTags);
433 }
434 else {
435 checkParamTags(tags, ast, reportExpectedTags);
436 }
437 final List<ExceptionInfo> throwed =
438 combineExceptionInfo(getThrows(ast), getThrowed(ast));
439 checkThrowsTags(tags, throwed, reportExpectedTags);
440 if (CheckUtil.isNonVoidMethod(ast)) {
441 checkReturnTag(tags, ast.getLineNo(), reportExpectedTags);
442 }
443 }
444 }
445 tags.stream().filter(javadocTag -> !javadocTag.isSeeOrInheritDocTag())
446 .forEach(javadocTag -> log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL));
447 }
448
449
450
451
452
453
454
455 private static List<DetailAST> getRecordComponents(final DetailAST recordDef) {
456 final List<DetailAST> components = new ArrayList<>();
457 final DetailAST recordDecl = recordDef.findFirstToken(TokenTypes.RECORD_COMPONENTS);
458
459 DetailAST child = recordDecl.getFirstChild();
460 while (child != null) {
461 if (child.getType() == TokenTypes.RECORD_COMPONENT_DEF) {
462 components.add(child.findFirstToken(TokenTypes.IDENT));
463 }
464 child = child.getNextSibling();
465 }
466 return components;
467 }
468
469
470
471
472
473
474
475 private static DetailAST getRecordDef(DetailAST ast) {
476 DetailAST current = ast;
477 while (current.getType() != TokenTypes.RECORD_DEF) {
478 current = current.getParent();
479 }
480 return current;
481 }
482
483
484
485
486
487
488
489
490
491 private boolean hasShortCircuitTag(final DetailAST ast, final List<JavadocTag> tags) {
492 boolean result = true;
493
494 if (tags.size() == 1
495 && tags.get(0).isInheritDocTag()) {
496
497 if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) {
498 log(ast, MSG_INVALID_INHERIT_DOC);
499 }
500 }
501 else {
502 result = false;
503 }
504 return result;
505 }
506
507
508
509
510
511
512
513
514 private static List<JavadocTag> getMethodTags(TextBlock comment) {
515 final String[] lines = comment.getText();
516 final List<JavadocTag> tags = new ArrayList<>();
517 int currentLine = comment.getStartLineNo() - 1;
518 final int startColumnNumber = comment.getStartColNo();
519
520 for (int i = 0; i < lines.length; i++) {
521 currentLine++;
522 final Matcher javadocArgMatcher =
523 MATCH_JAVADOC_ARG.matcher(lines[i]);
524 final Matcher javadocArgMissingDescriptionMatcher =
525 MATCH_JAVADOC_ARG_MISSING_DESCRIPTION.matcher(lines[i]);
526 final Matcher javadocNoargMatcher =
527 MATCH_JAVADOC_NOARG.matcher(lines[i]);
528 final Matcher noargCurlyMatcher =
529 MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]);
530 final Matcher noargMultilineStart =
531 MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]);
532
533 if (javadocArgMatcher.find()) {
534 final int col = calculateTagColumn(javadocArgMatcher, i, startColumnNumber);
535 tags.add(new JavadocTag(currentLine, col, javadocArgMatcher.group(1),
536 javadocArgMatcher.group(2)));
537 }
538 else if (javadocArgMissingDescriptionMatcher.find()) {
539 final int col = calculateTagColumn(javadocArgMissingDescriptionMatcher, i,
540 startColumnNumber);
541 tags.add(new JavadocTag(currentLine, col,
542 javadocArgMissingDescriptionMatcher.group(1),
543 javadocArgMissingDescriptionMatcher.group(2)));
544 }
545 else if (javadocNoargMatcher.find()) {
546 final int col = calculateTagColumn(javadocNoargMatcher, i, startColumnNumber);
547 tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher.group(1)));
548 }
549 else if (noargCurlyMatcher.find()) {
550 tags.add(new JavadocTag(currentLine, 0, noargCurlyMatcher.group(1)));
551 }
552 else if (noargMultilineStart.find()) {
553 tags.addAll(getMultilineNoArgTags(noargMultilineStart, lines, i, currentLine));
554 }
555 }
556 return tags;
557 }
558
559
560
561
562
563
564
565
566
567 private static int calculateTagColumn(MatchResult javadocTagMatchResult,
568 int lineNumber, int startColumnNumber) {
569 int col = javadocTagMatchResult.start(1) - 1;
570 if (lineNumber == 0) {
571 col += startColumnNumber;
572 }
573 return col;
574 }
575
576
577
578
579
580
581
582
583
584
585 private static List<JavadocTag> getMultilineNoArgTags(final Matcher noargMultilineStart,
586 final String[] lines, final int lineIndex, final int tagLine) {
587 int remIndex = lineIndex;
588 Matcher multilineCont;
589
590 do {
591 remIndex++;
592 multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]);
593 } while (!multilineCont.find());
594
595 final List<JavadocTag> tags = new ArrayList<>();
596 final String lFin = multilineCont.group(1);
597 if (!NEXT_TAG.equals(lFin)
598 && !END_JAVADOC.equals(lFin)) {
599 final String param1 = noargMultilineStart.group(1);
600 final int col = noargMultilineStart.start(1) - 1;
601
602 tags.add(new JavadocTag(tagLine, col, param1));
603 }
604
605 return tags;
606 }
607
608
609
610
611
612
613
614 private static List<DetailAST> getParameters(DetailAST ast) {
615 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
616 final List<DetailAST> returnValue = new ArrayList<>();
617
618 DetailAST child = params.getFirstChild();
619 while (child != null) {
620 final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
621 if (ident != null) {
622 returnValue.add(ident);
623 }
624 child = child.getNextSibling();
625 }
626 return returnValue;
627 }
628
629
630
631
632
633
634
635 private static List<ExceptionInfo> getThrows(DetailAST ast) {
636 final List<ExceptionInfo> returnValue = new ArrayList<>();
637 final DetailAST throwsAST = ast
638 .findFirstToken(TokenTypes.LITERAL_THROWS);
639 if (throwsAST != null) {
640 DetailAST child = throwsAST.getFirstChild();
641 while (child != null) {
642 if (child.getType() == TokenTypes.IDENT
643 || child.getType() == TokenTypes.DOT) {
644 returnValue.add(getExceptionInfo(child));
645 }
646 child = child.getNextSibling();
647 }
648 }
649 return returnValue;
650 }
651
652
653
654
655
656
657
658 private static List<ExceptionInfo> getThrowed(DetailAST methodAst) {
659 final List<ExceptionInfo> returnValue = new ArrayList<>();
660 final List<DetailAST> throwLiterals = findTokensInAstByType(methodAst,
661 TokenTypes.LITERAL_THROW);
662 for (DetailAST throwAst : throwLiterals) {
663 if (!isInIgnoreBlock(methodAst, throwAst)) {
664 final DetailAST newAst = throwAst.getFirstChild().getFirstChild();
665 if (newAst.getType() == TokenTypes.LITERAL_NEW) {
666 final DetailAST child = newAst.getFirstChild();
667 returnValue.add(getExceptionInfo(child));
668 }
669 }
670 }
671 return returnValue;
672 }
673
674
675
676
677
678
679
680 private static ExceptionInfo getExceptionInfo(DetailAST ast) {
681 final FullIdent ident = FullIdent.createFullIdent(ast);
682 final DetailAST firstClassNameNode = getFirstClassNameNode(ast);
683 return new ExceptionInfo(firstClassNameNode,
684 new ClassInfo(new Token(ident)));
685 }
686
687
688
689
690
691
692
693 private static DetailAST getFirstClassNameNode(DetailAST ast) {
694 DetailAST startNode = ast;
695 while (startNode.getType() == TokenTypes.DOT) {
696 startNode = startNode.getFirstChild();
697 }
698 return startNode;
699 }
700
701
702
703
704
705
706
707
708
709
710 private static boolean isInIgnoreBlock(DetailAST methodBodyAst, DetailAST throwAst) {
711 DetailAST ancestor = throwAst;
712 while (ancestor != methodBodyAst) {
713 if (ancestor.getType() == TokenTypes.LAMBDA
714 || ancestor.getType() == TokenTypes.OBJBLOCK
715 || ancestor.findFirstToken(TokenTypes.LITERAL_CATCH) != null) {
716
717
718 break;
719 }
720 if (ancestor.getType() == TokenTypes.LITERAL_CATCH
721 || ancestor.getType() == TokenTypes.LITERAL_FINALLY) {
722
723
724 ancestor = ancestor.getParent();
725 }
726 ancestor = ancestor.getParent();
727 }
728 return ancestor != methodBodyAst;
729 }
730
731
732
733
734
735
736
737
738 private static List<ExceptionInfo> combineExceptionInfo(Collection<ExceptionInfo> first,
739 Iterable<ExceptionInfo> second) {
740 final List<ExceptionInfo> result = new ArrayList<>(first);
741 for (ExceptionInfo exceptionInfo : second) {
742 if (result.stream().noneMatch(item -> isExceptionInfoSame(item, exceptionInfo))) {
743 result.add(exceptionInfo);
744 }
745 }
746 return result;
747 }
748
749
750
751
752
753
754
755
756
757 public static List<DetailAST> findTokensInAstByType(DetailAST root, int astType) {
758 final List<DetailAST> result = new ArrayList<>();
759
760 DetailAST curNode = root;
761 do {
762
763 if (curNode.getType() == astType) {
764 result.add(curNode);
765 }
766
767 if (curNode.hasChildren()) {
768 curNode = curNode.getFirstChild();
769 continue;
770 }
771
772 while (curNode.getNextSibling() == null) {
773 curNode = curNode.getParent();
774 }
775
776 if (curNode != root) {
777 curNode = curNode.getNextSibling();
778 }
779 } while (curNode != root);
780 return result;
781 }
782
783
784
785
786
787
788
789
790
791
792 private void checkRecordParamTags(final List<JavadocTag> tags,
793 final DetailAST compactDef, boolean reportExpectedTags) {
794
795 final DetailAST parent = getRecordDef(compactDef);
796 final List<DetailAST> params = getRecordComponents(parent);
797
798 final ListIterator<JavadocTag> tagIt = tags.listIterator();
799 while (tagIt.hasNext()) {
800 final JavadocTag tag = tagIt.next();
801
802 if (!tag.isParamTag()) {
803 continue;
804 }
805
806 tagIt.remove();
807
808 final String arg1 = tag.getFirstArg();
809 final boolean found = removeMatchingParam(params, arg1);
810
811 if (!found) {
812 log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
813 JavadocTagInfo.PARAM.getText(), arg1);
814 }
815 }
816
817 if (!allowMissingParamTags && reportExpectedTags) {
818 for (DetailAST param : params) {
819 log(compactDef, MSG_EXPECTED_TAG,
820 JavadocTagInfo.PARAM.getText(), param.getText());
821 }
822 }
823 }
824
825
826
827
828
829
830
831
832
833 private void checkParamTags(final List<JavadocTag> tags,
834 final DetailAST parent, boolean reportExpectedTags) {
835 final List<DetailAST> params = getParameters(parent);
836 final List<DetailAST> typeParams = CheckUtil
837 .getTypeParameters(parent);
838
839
840 final ListIterator<JavadocTag> tagIt = tags.listIterator();
841 while (tagIt.hasNext()) {
842 final JavadocTag tag = tagIt.next();
843
844 if (!tag.isParamTag()) {
845 continue;
846 }
847
848 tagIt.remove();
849
850 final String arg1 = tag.getFirstArg();
851 boolean found = removeMatchingParam(params, arg1);
852
853 if (arg1.endsWith(ELEMENT_END)) {
854 found = searchMatchingTypeParameter(typeParams,
855 arg1.substring(1, arg1.length() - 1));
856 }
857
858
859 if (!found) {
860 log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG,
861 JavadocTagInfo.PARAM.getText(), arg1);
862 }
863 }
864
865
866
867 if (!allowMissingParamTags && reportExpectedTags) {
868 for (DetailAST param : params) {
869 log(param, MSG_EXPECTED_TAG,
870 JavadocTagInfo.PARAM.getText(), param.getText());
871 }
872
873 for (DetailAST typeParam : typeParams) {
874 log(typeParam, MSG_EXPECTED_TAG,
875 JavadocTagInfo.PARAM.getText(),
876 ELEMENT_START + typeParam.findFirstToken(TokenTypes.IDENT).getText()
877 + ELEMENT_END);
878 }
879 }
880 }
881
882
883
884
885
886
887
888
889
890
891 private static boolean searchMatchingTypeParameter(Iterable<DetailAST> typeParams,
892 String requiredTypeName) {
893
894 final Iterator<DetailAST> typeParamsIt = typeParams.iterator();
895 boolean found = false;
896 while (typeParamsIt.hasNext()) {
897 final DetailAST typeParam = typeParamsIt.next();
898 if (typeParam.findFirstToken(TokenTypes.IDENT).getText()
899 .equals(requiredTypeName)) {
900 found = true;
901 typeParamsIt.remove();
902 break;
903 }
904 }
905 return found;
906 }
907
908
909
910
911
912
913
914
915 private static boolean removeMatchingParam(Iterable<DetailAST> params, String paramName) {
916 boolean found = false;
917 final Iterator<DetailAST> paramIt = params.iterator();
918 while (paramIt.hasNext()) {
919 final DetailAST param = paramIt.next();
920 if (param.getText().equals(paramName)) {
921 found = true;
922 paramIt.remove();
923 break;
924 }
925 }
926 return found;
927 }
928
929
930
931
932
933
934
935
936
937
938 private void checkReturnTag(List<JavadocTag> tags, int lineNo,
939 boolean reportExpectedTags) {
940
941
942 boolean found = false;
943 final ListIterator<JavadocTag> it = tags.listIterator();
944 while (it.hasNext()) {
945 final JavadocTag javadocTag = it.next();
946 if (javadocTag.isReturnTag()) {
947 if (found) {
948 log(javadocTag.getLineNo(), javadocTag.getColumnNo(),
949 MSG_DUPLICATE_TAG,
950 JavadocTagInfo.RETURN.getText());
951 }
952 found = true;
953 it.remove();
954 }
955 }
956
957
958
959 if (!found && !allowMissingReturnTag && reportExpectedTags) {
960 log(lineNo, MSG_RETURN_EXPECTED);
961 }
962 }
963
964
965
966
967
968
969
970
971
972 private void checkThrowsTags(List<JavadocTag> tags,
973 List<ExceptionInfo> throwsList, boolean reportExpectedTags) {
974
975 final ListIterator<JavadocTag> tagIt = tags.listIterator();
976 while (tagIt.hasNext()) {
977 final JavadocTag tag = tagIt.next();
978
979 if (!tag.isThrowsTag()) {
980 continue;
981 }
982 tagIt.remove();
983
984
985 processThrows(throwsList, tag.getFirstArg());
986 }
987
988
989 if (validateThrows && reportExpectedTags) {
990 throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound())
991 .forEach(exceptionInfo -> {
992 final Token token = exceptionInfo.getName();
993 log(exceptionInfo.getAst(),
994 MSG_EXPECTED_TAG,
995 JavadocTagInfo.THROWS.getText(), token.getText());
996 });
997 }
998 }
999
1000
1001
1002
1003
1004
1005
1006 private static void processThrows(Iterable<ExceptionInfo> throwsIterable,
1007 String documentedClassName) {
1008 for (ExceptionInfo exceptionInfo : throwsIterable) {
1009 if (isClassNamesSame(exceptionInfo.getName().getText(),
1010 documentedClassName)) {
1011 exceptionInfo.setFound();
1012 break;
1013 }
1014 }
1015 }
1016
1017
1018
1019
1020
1021
1022
1023
1024 private static boolean isExceptionInfoSame(ExceptionInfo info1, ExceptionInfo info2) {
1025 return isClassNamesSame(info1.getName().getText(),
1026 info2.getName().getText());
1027 }
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037 private static boolean isClassNamesSame(String class1, String class2) {
1038 final String class1ShortName = class1
1039 .substring(class1.lastIndexOf('.') + 1);
1040 final String class2ShortName = class2
1041 .substring(class2.lastIndexOf('.') + 1);
1042 return class1ShortName.equals(class2ShortName);
1043 }
1044
1045
1046
1047
1048 private static class ClassInfo {
1049
1050
1051 private final Token name;
1052
1053
1054
1055
1056
1057
1058
1059 protected ClassInfo(final Token className) {
1060 name = className;
1061 }
1062
1063
1064
1065
1066
1067
1068 public final Token getName() {
1069 return name;
1070 }
1071
1072 }
1073
1074
1075
1076
1077 private static final class Token {
1078
1079
1080 private final int columnNo;
1081
1082 private final int lineNo;
1083
1084 private final String text;
1085
1086
1087
1088
1089
1090
1091
1092
1093 private Token(String text, int lineNo, int columnNo) {
1094 this.text = text;
1095 this.lineNo = lineNo;
1096 this.columnNo = columnNo;
1097 }
1098
1099
1100
1101
1102
1103
1104 private Token(FullIdent fullIdent) {
1105 text = fullIdent.getText();
1106 lineNo = fullIdent.getLineNo();
1107 columnNo = fullIdent.getColumnNo();
1108 }
1109
1110
1111
1112
1113
1114
1115 public String getText() {
1116 return text;
1117 }
1118
1119 @Override
1120 public String toString() {
1121 return "Token[" + text + "(" + lineNo
1122 + "x" + columnNo + ")]";
1123 }
1124
1125 }
1126
1127
1128 private static final class ExceptionInfo {
1129
1130
1131 private final DetailAST ast;
1132
1133
1134 private final ClassInfo classInfo;
1135
1136 private boolean found;
1137
1138
1139
1140
1141
1142
1143
1144 private ExceptionInfo(DetailAST ast, ClassInfo classInfo) {
1145 this.ast = ast;
1146 this.classInfo = classInfo;
1147 }
1148
1149
1150
1151
1152
1153
1154 private DetailAST getAst() {
1155 return ast;
1156 }
1157
1158
1159 private void setFound() {
1160 found = true;
1161 }
1162
1163
1164
1165
1166
1167
1168 private boolean isFound() {
1169 return found;
1170 }
1171
1172
1173
1174
1175
1176
1177 private Token getName() {
1178 return classInfo.getName();
1179 }
1180
1181 }
1182
1183 }