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.design;
21
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Set;
27 import java.util.regex.Pattern;
28 import java.util.stream.Collectors;
29
30 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
31 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
32 import com.puppycrawl.tools.checkstyle.api.DetailAST;
33 import com.puppycrawl.tools.checkstyle.api.FullIdent;
34 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
35 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
36 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
37 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
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
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 @FileStatefulCheck
179 public class VisibilityModifierCheck
180 extends AbstractCheck {
181
182
183
184
185
186 public static final String MSG_KEY = "variable.notPrivate";
187
188
189 private static final Set<String> DEFAULT_IMMUTABLE_TYPES = Set.of(
190 "java.lang.String",
191 "java.lang.Integer",
192 "java.lang.Byte",
193 "java.lang.Character",
194 "java.lang.Short",
195 "java.lang.Boolean",
196 "java.lang.Long",
197 "java.lang.Double",
198 "java.lang.Float",
199 "java.lang.StackTraceElement",
200 "java.math.BigInteger",
201 "java.math.BigDecimal",
202 "java.io.File",
203 "java.util.Locale",
204 "java.util.UUID",
205 "java.net.URL",
206 "java.net.URI",
207 "java.net.Inet4Address",
208 "java.net.Inet6Address",
209 "java.net.InetSocketAddress"
210 );
211
212
213 private static final Set<String> DEFAULT_IGNORE_ANNOTATIONS = Set.of(
214 "org.junit.Rule",
215 "org.junit.ClassRule",
216 "com.google.common.annotations.VisibleForTesting"
217 );
218
219
220 private static final String PUBLIC_ACCESS_MODIFIER = "public";
221
222
223 private static final String PRIVATE_ACCESS_MODIFIER = "private";
224
225
226 private static final String PROTECTED_ACCESS_MODIFIER = "protected";
227
228
229 private static final String PACKAGE_ACCESS_MODIFIER = "package";
230
231
232 private static final String STATIC_KEYWORD = "static";
233
234
235 private static final String FINAL_KEYWORD = "final";
236
237
238 private static final String[] EXPLICIT_MODS = {
239 PUBLIC_ACCESS_MODIFIER,
240 PRIVATE_ACCESS_MODIFIER,
241 PROTECTED_ACCESS_MODIFIER,
242 };
243
244
245
246
247 private Pattern publicMemberPattern = Pattern.compile("^serialVersionUID$");
248
249
250 private Set<String> ignoreAnnotationShortNames;
251
252
253 private Set<String> immutableClassShortNames;
254
255
256
257
258
259 private Set<String> ignoreAnnotationCanonicalNames = DEFAULT_IGNORE_ANNOTATIONS;
260
261
262 private boolean protectedAllowed;
263
264
265 private boolean packageAllowed;
266
267
268 private boolean allowPublicImmutableFields;
269
270
271 private boolean allowPublicFinalFields;
272
273
274 private Set<String> immutableClassCanonicalNames = DEFAULT_IMMUTABLE_TYPES;
275
276
277
278
279
280
281
282
283 public void setIgnoreAnnotationCanonicalNames(String... annotationNames) {
284 ignoreAnnotationCanonicalNames = Set.of(annotationNames);
285 }
286
287
288
289
290
291
292
293 public void setProtectedAllowed(boolean protectedAllowed) {
294 this.protectedAllowed = protectedAllowed;
295 }
296
297
298
299
300
301
302
303 public void setPackageAllowed(boolean packageAllowed) {
304 this.packageAllowed = packageAllowed;
305 }
306
307
308
309
310
311
312
313
314 public void setPublicMemberPattern(Pattern pattern) {
315 publicMemberPattern = pattern;
316 }
317
318
319
320
321
322
323
324 public void setAllowPublicImmutableFields(boolean allow) {
325 allowPublicImmutableFields = allow;
326 }
327
328
329
330
331
332
333
334 public void setAllowPublicFinalFields(boolean allow) {
335 allowPublicFinalFields = allow;
336 }
337
338
339
340
341
342
343
344 public void setImmutableClassCanonicalNames(String... classNames) {
345 immutableClassCanonicalNames = Set.of(classNames);
346 }
347
348 @Override
349 public int[] getDefaultTokens() {
350 return getRequiredTokens();
351 }
352
353 @Override
354 public int[] getAcceptableTokens() {
355 return getRequiredTokens();
356 }
357
358 @Override
359 public int[] getRequiredTokens() {
360 return new int[] {
361 TokenTypes.VARIABLE_DEF,
362 TokenTypes.IMPORT,
363 };
364 }
365
366 @Override
367 public void beginTree(DetailAST rootAst) {
368 immutableClassShortNames = getClassShortNames(immutableClassCanonicalNames);
369 ignoreAnnotationShortNames = getClassShortNames(ignoreAnnotationCanonicalNames);
370 }
371
372 @Override
373 public void visitToken(DetailAST ast) {
374 switch (ast.getType()) {
375 case TokenTypes.VARIABLE_DEF:
376 if (!isAnonymousClassVariable(ast)) {
377 visitVariableDef(ast);
378 }
379 break;
380 case TokenTypes.IMPORT:
381 visitImport(ast);
382 break;
383 default:
384 final String exceptionMsg = "Unexpected token type: " + ast.getText();
385 throw new IllegalArgumentException(exceptionMsg);
386 }
387 }
388
389
390
391
392
393
394
395 private static boolean isAnonymousClassVariable(DetailAST variableDef) {
396 return variableDef.getParent().getType() != TokenTypes.OBJBLOCK;
397 }
398
399
400
401
402
403
404
405 private void visitVariableDef(DetailAST variableDef) {
406 final boolean inInterfaceOrAnnotationBlock =
407 ScopeUtil.isInInterfaceOrAnnotationBlock(variableDef);
408
409 if (!inInterfaceOrAnnotationBlock && !hasIgnoreAnnotation(variableDef)) {
410 final DetailAST varNameAST = variableDef.findFirstToken(TokenTypes.TYPE)
411 .getNextSibling();
412 final String varName = varNameAST.getText();
413 if (!hasProperAccessModifier(variableDef, varName)) {
414 log(varNameAST, MSG_KEY, varName);
415 }
416 }
417 }
418
419
420
421
422
423
424
425 private boolean hasIgnoreAnnotation(DetailAST variableDef) {
426 final DetailAST firstIgnoreAnnotation =
427 findMatchingAnnotation(variableDef);
428 return firstIgnoreAnnotation != null;
429 }
430
431
432
433
434
435
436
437
438 private void visitImport(DetailAST importAst) {
439 if (!isStarImport(importAst)) {
440 final String canonicalName = getCanonicalName(importAst);
441 final String shortName = getClassShortName(canonicalName);
442
443
444
445
446 if (!immutableClassCanonicalNames.contains(canonicalName)) {
447 immutableClassShortNames.remove(shortName);
448 }
449 if (!ignoreAnnotationCanonicalNames.contains(canonicalName)) {
450 ignoreAnnotationShortNames.remove(shortName);
451 }
452 }
453 }
454
455
456
457
458
459
460
461
462
463
464
465
466
467 private static boolean isStarImport(DetailAST importAst) {
468 boolean result = false;
469 DetailAST toVisit = importAst;
470 while (toVisit != null) {
471 toVisit = getNextSubTreeNode(toVisit, importAst);
472 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) {
473 result = true;
474 break;
475 }
476 }
477 return result;
478 }
479
480
481
482
483
484
485
486
487 private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) {
488 boolean result = true;
489
490 final String variableScope = getVisibilityScope(variableDef);
491
492 if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) {
493 result =
494 isStaticFinalVariable(variableDef)
495 || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope)
496 || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope)
497 || isIgnoredPublicMember(variableName, variableScope)
498 || isAllowedPublicField(variableDef);
499 }
500
501 return result;
502 }
503
504
505
506
507
508
509
510 private static boolean isStaticFinalVariable(DetailAST variableDef) {
511 final Set<String> modifiers = getModifiers(variableDef);
512 return modifiers.contains(STATIC_KEYWORD)
513 && modifiers.contains(FINAL_KEYWORD);
514 }
515
516
517
518
519
520
521
522
523 private boolean isIgnoredPublicMember(String variableName, String variableScope) {
524 return PUBLIC_ACCESS_MODIFIER.equals(variableScope)
525 && publicMemberPattern.matcher(variableName).find();
526 }
527
528
529
530
531
532
533
534 private boolean isAllowedPublicField(DetailAST variableDef) {
535 return allowPublicFinalFields && isFinalField(variableDef)
536 || allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef);
537 }
538
539
540
541
542
543
544
545 private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) {
546 final DetailAST classDef = variableDef.getParent().getParent();
547 final Set<String> classModifiers = getModifiers(classDef);
548 return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF)
549 && isImmutableField(variableDef);
550 }
551
552
553
554
555
556
557
558 private static Set<String> getModifiers(DetailAST defAST) {
559 final DetailAST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS);
560 final Set<String> modifiersSet = new HashSet<>();
561 if (modifiersAST != null) {
562 DetailAST modifier = modifiersAST.getFirstChild();
563 while (modifier != null) {
564 modifiersSet.add(modifier.getText());
565 modifier = modifier.getNextSibling();
566 }
567 }
568 return modifiersSet;
569 }
570
571
572
573
574
575
576
577 private static String getVisibilityScope(DetailAST variableDef) {
578 final Set<String> modifiers = getModifiers(variableDef);
579 String accessModifier = PACKAGE_ACCESS_MODIFIER;
580 for (final String modifier : EXPLICIT_MODS) {
581 if (modifiers.contains(modifier)) {
582 accessModifier = modifier;
583 break;
584 }
585 }
586 return accessModifier;
587 }
588
589
590
591
592
593
594
595
596
597
598
599 private boolean isImmutableField(DetailAST variableDef) {
600 boolean result = false;
601 if (isFinalField(variableDef)) {
602 final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE);
603 final boolean isCanonicalName = isCanonicalName(type);
604 final String typeName = getCanonicalName(type);
605 if (immutableClassShortNames.contains(typeName)
606 || isCanonicalName && immutableClassCanonicalNames.contains(typeName)) {
607 final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName);
608
609 if (typeArgs == null) {
610 result = true;
611 }
612 else {
613 final List<String> argsClassNames = getTypeArgsClassNames(typeArgs);
614 result = areImmutableTypeArguments(argsClassNames);
615 }
616 }
617 else {
618 result = !isCanonicalName && isPrimitive(type);
619 }
620 }
621 return result;
622 }
623
624
625
626
627
628
629
630 private static boolean isCanonicalName(DetailAST type) {
631 return type.getFirstChild().getType() == TokenTypes.DOT;
632 }
633
634
635
636
637
638
639
640
641 private static DetailAST getGenericTypeArgs(DetailAST type, boolean isCanonicalName) {
642 final DetailAST typeArgs;
643 if (isCanonicalName) {
644
645 typeArgs = type.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS);
646 }
647 else {
648 typeArgs = type.findFirstToken(TokenTypes.TYPE_ARGUMENTS);
649 }
650 return typeArgs;
651 }
652
653
654
655
656
657
658
659 private static List<String> getTypeArgsClassNames(DetailAST typeArgs) {
660 final List<String> typeClassNames = new ArrayList<>();
661 DetailAST type = typeArgs.findFirstToken(TokenTypes.TYPE_ARGUMENT);
662 DetailAST sibling;
663 do {
664 final String typeName = getCanonicalName(type);
665 typeClassNames.add(typeName);
666 sibling = type.getNextSibling();
667 type = sibling.getNextSibling();
668 } while (sibling.getType() == TokenTypes.COMMA);
669 return typeClassNames;
670 }
671
672
673
674
675
676
677
678
679
680 private boolean areImmutableTypeArguments(Collection<String> typeArgsClassNames) {
681 return typeArgsClassNames.stream().noneMatch(
682 typeName -> {
683 return !immutableClassShortNames.contains(typeName)
684 && !immutableClassCanonicalNames.contains(typeName);
685 });
686 }
687
688
689
690
691
692
693
694 private static boolean isFinalField(DetailAST variableDef) {
695 final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS);
696 return modifiers.findFirstToken(TokenTypes.FINAL) != null;
697 }
698
699
700
701
702
703
704
705
706
707
708
709 private static boolean isPrimitive(DetailAST type) {
710 return type.getFirstChild().getType() != TokenTypes.IDENT;
711 }
712
713
714
715
716
717
718
719 private static String getCanonicalName(DetailAST type) {
720 final StringBuilder canonicalNameBuilder = new StringBuilder(256);
721 DetailAST toVisit = type;
722 while (toVisit != null) {
723 toVisit = getNextSubTreeNode(toVisit, type);
724 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) {
725 if (canonicalNameBuilder.length() > 0) {
726 canonicalNameBuilder.append('.');
727 }
728 canonicalNameBuilder.append(toVisit.getText());
729 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type);
730 if (nextSubTreeNode != null
731 && nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) {
732 break;
733 }
734 }
735 }
736 return canonicalNameBuilder.toString();
737 }
738
739
740
741
742
743
744
745
746
747
748 private static DetailAST
749 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) {
750 DetailAST currentNode = currentNodeAst;
751 DetailAST toVisitAst = currentNode.getFirstChild();
752 while (toVisitAst == null) {
753 toVisitAst = currentNode.getNextSibling();
754 if (currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) {
755 break;
756 }
757 currentNode = currentNode.getParent();
758 }
759 return toVisitAst;
760 }
761
762
763
764
765
766
767
768 private static Set<String> getClassShortNames(Set<String> canonicalClassNames) {
769 return canonicalClassNames.stream()
770 .map(CommonUtil::baseClassName)
771 .collect(Collectors.toCollection(HashSet::new));
772 }
773
774
775
776
777
778
779
780 private static String getClassShortName(String canonicalClassName) {
781 return canonicalClassName
782 .substring(canonicalClassName.lastIndexOf('.') + 1);
783 }
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808 private DetailAST findMatchingAnnotation(DetailAST variableDef) {
809 DetailAST matchingAnnotation = null;
810
811 final DetailAST holder = AnnotationUtil.getAnnotationHolder(variableDef);
812
813 for (DetailAST child = holder.getFirstChild();
814 child != null; child = child.getNextSibling()) {
815 if (child.getType() == TokenTypes.ANNOTATION) {
816 final DetailAST ast = child.getFirstChild();
817 final String name =
818 FullIdent.createFullIdent(ast.getNextSibling()).getText();
819 if (ignoreAnnotationCanonicalNames.contains(name)
820 || ignoreAnnotationShortNames.contains(name)) {
821 matchingAnnotation = child;
822 break;
823 }
824 }
825 }
826
827 return matchingAnnotation;
828 }
829
830 }