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