001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.design; 021 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Set; 027import java.util.regex.Pattern; 028import java.util.stream.Collectors; 029 030import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 031import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 032import com.puppycrawl.tools.checkstyle.api.DetailAST; 033import com.puppycrawl.tools.checkstyle.api.FullIdent; 034import com.puppycrawl.tools.checkstyle.api.TokenTypes; 035import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 036import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 037import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 038 039/** 040 * <p> 041 * Checks visibility of class members. Only static final, immutable or annotated 042 * by specified annotation members may be public; 043 * other class members must be private unless the property {@code protectedAllowed} 044 * or {@code packageAllowed} is set. 045 * </p> 046 * <p> 047 * Public members are not flagged if the name matches the public 048 * member regular expression (contains {@code "^serialVersionUID$"} by 049 * default). 050 * </p> 051 * <p> 052 * Note that Checkstyle 2 used to include {@code "^f[A-Z][a-zA-Z0-9]*$"} in the default pattern 053 * to allow names used in container-managed persistence for Enterprise JavaBeans (EJB) 1.1 with 054 * the default settings. With EJB 2.0 it is no longer necessary to have public access for 055 * persistent fields, so the default has been changed. 056 * </p> 057 * <p> 058 * Rationale: Enforce encapsulation. 059 * </p> 060 * <p> 061 * Check also has options making it less strict: 062 * </p> 063 * <p> 064 * <b>ignoreAnnotationCanonicalNames</b>- the list of annotations which ignore 065 * variables in consideration. If user want to provide short annotation name that 066 * type will match to any named the same type without consideration of package. 067 * </p> 068 * <p> 069 * <b>allowPublicFinalFields</b>- which allows public final fields. 070 * </p> 071 * <p> 072 * <b>allowPublicImmutableFields</b>- which allows immutable fields to be 073 * declared as public if defined in final class. 074 * </p> 075 * <p> 076 * Field is known to be immutable if: 077 * </p> 078 * <ul> 079 * <li>It's declared as final</li> 080 * <li>Has either a primitive type or instance of class user defined to be immutable 081 * (such as String, ImmutableCollection from Guava, etc.)</li> 082 * </ul> 083 * <p> 084 * Classes known to be immutable are listed in <b>immutableClassCanonicalNames</b> 085 * by their canonical names. 086 * </p> 087 * <p> 088 * Property Rationale: Forcing all fields of class to have private modifier by default is 089 * good in most cases, but in some cases it drawbacks in too much boilerplate get/set code. 090 * One of such cases are immutable classes. 091 * </p> 092 * <p> 093 * Restriction: Check doesn't check if class is immutable, there's no checking 094 * if accessory methods are missing and all fields are immutable, we only check 095 * if current field is immutable or final. 096 * Under the flag <b>allowPublicImmutableFields</b>, the enclosing class must 097 * also be final, to encourage immutability. 098 * Under the flag <b>allowPublicFinalFields</b>, the final modifier 099 * on the enclosing class is optional. 100 * </p> 101 * <p> 102 * Star imports are out of scope of this Check. So if one of type imported via 103 * star import collides with user specified one by its short name - there 104 * won't be Check's violation. 105 * </p> 106 * <ul> 107 * <li> 108 * Property {@code packageAllowed} - Control whether package visible members are allowed. 109 * Type is {@code boolean}. 110 * Default value is {@code false}. 111 * </li> 112 * <li> 113 * Property {@code protectedAllowed} - Control whether protected members are allowed. 114 * Type is {@code boolean}. 115 * Default value is {@code false}. 116 * </li> 117 * <li> 118 * Property {@code publicMemberPattern} - Specify pattern for public members that should be ignored. 119 * Type is {@code java.util.regex.Pattern}. 120 * Default value is {@code "^serialVersionUID$"}. 121 * </li> 122 * <li> 123 * Property {@code allowPublicFinalFields} - Allow final fields to be declared as public. 124 * Type is {@code boolean}. 125 * Default value is {@code false}. 126 * </li> 127 * <li> 128 * Property {@code allowPublicImmutableFields} - Allow immutable fields to be 129 * declared as public if defined in final class. 130 * Type is {@code boolean}. 131 * Default value is {@code false}. 132 * </li> 133 * <li> 134 * Property {@code immutableClassCanonicalNames} - Specify immutable classes canonical names. 135 * Type is {@code java.lang.String[]}. 136 * Default value is {@code java.io.File, java.lang.Boolean, java.lang.Byte, 137 * java.lang.Character, java.lang.Double, java.lang.Float, java.lang.Integer, 138 * java.lang.Long, java.lang.Short, java.lang.StackTraceElement, java.lang.String, 139 * java.math.BigDecimal, java.math.BigInteger, java.net.Inet4Address, java.net.Inet6Address, 140 * java.net.InetSocketAddress, java.net.URI, java.net.URL, java.util.Locale, java.util.UUID}. 141 * </li> 142 * <li> 143 * Property {@code ignoreAnnotationCanonicalNames} - Specify annotations canonical 144 * names which ignore variables in consideration. 145 * Type is {@code java.lang.String[]}. 146 * Default value is {@code com.google.common.annotations.VisibleForTesting, 147 * org.junit.ClassRule, org.junit.Rule}. 148 * </li> 149 * </ul> 150 * <p> 151 * To configure the check: 152 * </p> 153 * <pre> 154 * <module name="VisibilityModifier"/> 155 * </pre> 156 * <p> 157 * To configure the check so that it allows package visible members: 158 * </p> 159 * <pre> 160 * <module name="VisibilityModifier"> 161 * <property name="packageAllowed" value="true"/> 162 * </module> 163 * </pre> 164 * <p> 165 * To configure the check so that it allows no public members: 166 * </p> 167 * <pre> 168 * <module name="VisibilityModifier"> 169 * <property name="publicMemberPattern" value="^$"/> 170 * </module> 171 * </pre> 172 * <p> 173 * To configure the Check so that it allows public immutable fields (mostly for immutable classes): 174 * </p> 175 * <pre> 176 * <module name="VisibilityModifier"> 177 * <property name="allowPublicImmutableFields" value="true"/> 178 * </module> 179 * </pre> 180 * <p> 181 * Example of allowed public immutable fields: 182 * </p> 183 * <pre> 184 * public class ImmutableClass 185 * { 186 * public final ImmutableSet<String> includes; // No warning 187 * public final ImmutableSet<String> excludes; // No warning 188 * public final java.lang.String notes; // No warning 189 * public final BigDecimal value; // No warning 190 * 191 * public ImmutableClass(Collection<String> includes, Collection<String> excludes, 192 * BigDecimal value, String notes) 193 * { 194 * this.includes = ImmutableSet.copyOf(includes); 195 * this.excludes = ImmutableSet.copyOf(excludes); 196 * this.value = value; 197 * this.notes = notes; 198 * } 199 * } 200 * </pre> 201 * <p> 202 * To configure the Check in order to allow user specified immutable class names: 203 * </p> 204 * <pre> 205 * <module name="VisibilityModifier"> 206 * <property name="allowPublicImmutableFields" value="true"/> 207 * <property name="immutableClassCanonicalNames" value=" 208 * com.google.common.collect.ImmutableSet"/> 209 * </module> 210 * </pre> 211 * <p> 212 * Example of allowed public immutable fields: 213 * </p> 214 * <pre> 215 * public class ImmutableClass 216 * { 217 * public final ImmutableSet<String> includes; // No warning 218 * public final ImmutableSet<String> excludes; // No warning 219 * public final java.lang.String notes; // Warning here because 220 * //'java.lang.String' wasn't specified as allowed class 221 * public final int someValue; // No warning 222 * 223 * public ImmutableClass(Collection<String> includes, Collection<String> excludes, 224 * String notes, int someValue) 225 * { 226 * this.includes = ImmutableSet.copyOf(includes); 227 * this.excludes = ImmutableSet.copyOf(excludes); 228 * this.value = value; 229 * this.notes = notes; 230 * this.someValue = someValue; 231 * } 232 * } 233 * </pre> 234 * <p> 235 * Note, if allowPublicImmutableFields is set to true, the check will also check 236 * whether generic type parameters are immutable. If at least one generic type 237 * parameter is mutable, there will be a violation. 238 * </p> 239 * <pre> 240 * <module name="VisibilityModifier"> 241 * <property name="allowPublicImmutableFields" value="true"/> 242 * <property name="immutableClassCanonicalNames" 243 * value="com.google.common.collect.ImmutableSet, com.google.common.collect.ImmutableMap, 244 * java.lang.String"/> 245 * </module> 246 * </pre> 247 * <p> 248 * Example of how the check works: 249 * </p> 250 * <pre> 251 * public final class Test { 252 * public final String s; 253 * public final ImmutableSet<String> names; 254 * public final ImmutableSet<Object> objects; // violation (Object class is mutable) 255 * public final ImmutableMap<String, Object> links; // violation (Object class is mutable) 256 * 257 * public Test() { 258 * s = "Hello!"; 259 * names = ImmutableSet.of(); 260 * objects = ImmutableSet.of(); 261 * links = ImmutableMap.of(); 262 * } 263 * } 264 * </pre> 265 * <p> 266 * To configure the Check passing fields annotated with @com.annotation.CustomAnnotation: 267 * </p> 268 * <pre> 269 * <module name="VisibilityModifier"> 270 * <property name="ignoreAnnotationCanonicalNames" value= 271 * "com.annotation.CustomAnnotation"/> 272 * </module> 273 * </pre> 274 * <p> 275 * Example of allowed field: 276 * </p> 277 * <pre> 278 * class SomeClass 279 * { 280 * @com.annotation.CustomAnnotation 281 * String annotatedString; // no warning 282 * @CustomAnnotation 283 * String shortCustomAnnotated; // no warning 284 * } 285 * </pre> 286 * <p> 287 * To configure the Check passing fields annotated with @org.junit.Rule, 288 * @org.junit.ClassRule and @com.google.common.annotations.VisibleForTesting annotations: 289 * </p> 290 * <pre> 291 * <module name="VisibilityModifier"/> 292 * </pre> 293 * <p> 294 * Example of allowed fields: 295 * </p> 296 * <pre> 297 * class SomeClass 298 * { 299 * @org.junit.Rule 300 * public TemporaryFolder publicJUnitRule = new TemporaryFolder(); // no warning 301 * @org.junit.ClassRule 302 * public static TemporaryFolder publicJUnitClassRule = new TemporaryFolder(); // no warning 303 * @com.google.common.annotations.VisibleForTesting 304 * public String testString = ""; // no warning 305 * } 306 * </pre> 307 * <p> 308 * To configure the Check passing fields annotated with short annotation name: 309 * </p> 310 * <pre> 311 * <module name="VisibilityModifier"> 312 * <property name="ignoreAnnotationCanonicalNames" 313 * value="CustomAnnotation"/> 314 * </module> 315 * </pre> 316 * <p> 317 * Example of allowed fields: 318 * </p> 319 * <pre> 320 * class SomeClass 321 * { 322 * @CustomAnnotation 323 * String customAnnotated; // no warning 324 * @com.annotation.CustomAnnotation 325 * String customAnnotated1; // no warning 326 * @mypackage.annotation.CustomAnnotation 327 * String customAnnotatedAnotherPackage; // another package but short name matches 328 * // so no violation 329 * } 330 * </pre> 331 * <p> 332 * To understand the difference between allowPublicImmutableFields and allowPublicFinalFields 333 * options, please, study the following examples. 334 * </p> 335 * <p> 336 * 1) To configure the check to use only 'allowPublicImmutableFields' option: 337 * </p> 338 * <pre> 339 * <module name="VisibilityModifier"> 340 * <property name="allowPublicImmutableFields" value="true"/> 341 * </module> 342 * </pre> 343 * <p> 344 * Code example: 345 * </p> 346 * <pre> 347 * public class InputPublicImmutable { 348 * public final int someIntValue; // violation 349 * public final ImmutableSet<String> includes; // violation 350 * public final java.lang.String notes; // violation 351 * public final BigDecimal value; // violation 352 * public final List list; // violation 353 * 354 * public InputPublicImmutable(Collection<String> includes, 355 * BigDecimal value, String notes, int someValue, List l) { 356 * this.includes = ImmutableSet.copyOf(includes); 357 * this.value = value; 358 * this.notes = notes; 359 * this.someIntValue = someValue; 360 * this.list = l; 361 * } 362 * } 363 * </pre> 364 * <p> 365 * 2) To configure the check to use only 'allowPublicFinalFields' option: 366 * </p> 367 * <pre> 368 * <module name="VisibilityModifier"> 369 * <property name="allowPublicFinalFields" value="true"/> 370 * </module> 371 * </pre> 372 * <p> 373 * Code example: 374 * </p> 375 * <pre> 376 * public class InputPublicImmutable { 377 * public final int someIntValue; 378 * public final ImmutableSet<String> includes; 379 * public final java.lang.String notes; 380 * public final BigDecimal value; 381 * public final List list; 382 * 383 * public InputPublicImmutable(Collection<String> includes, 384 * BigDecimal value, String notes, int someValue, List l) { 385 * this.includes = ImmutableSet.copyOf(includes); 386 * this.value = value; 387 * this.notes = notes; 388 * this.someIntValue = someValue; 389 * this.list = l; 390 * } 391 * } 392 * </pre> 393 * <p> 394 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 395 * </p> 396 * <p> 397 * Violation Message Keys: 398 * </p> 399 * <ul> 400 * <li> 401 * {@code variable.notPrivate} 402 * </li> 403 * </ul> 404 * 405 * @since 3.0 406 */ 407@FileStatefulCheck 408public class VisibilityModifierCheck 409 extends AbstractCheck { 410 411 /** 412 * A key is pointing to the warning message text in "messages.properties" 413 * file. 414 */ 415 public static final String MSG_KEY = "variable.notPrivate"; 416 417 /** Default immutable types canonical names. */ 418 private static final Set<String> DEFAULT_IMMUTABLE_TYPES = Set.of( 419 "java.lang.String", 420 "java.lang.Integer", 421 "java.lang.Byte", 422 "java.lang.Character", 423 "java.lang.Short", 424 "java.lang.Boolean", 425 "java.lang.Long", 426 "java.lang.Double", 427 "java.lang.Float", 428 "java.lang.StackTraceElement", 429 "java.math.BigInteger", 430 "java.math.BigDecimal", 431 "java.io.File", 432 "java.util.Locale", 433 "java.util.UUID", 434 "java.net.URL", 435 "java.net.URI", 436 "java.net.Inet4Address", 437 "java.net.Inet6Address", 438 "java.net.InetSocketAddress" 439 ); 440 441 /** Default ignore annotations canonical names. */ 442 private static final Set<String> DEFAULT_IGNORE_ANNOTATIONS = Set.of( 443 "org.junit.Rule", 444 "org.junit.ClassRule", 445 "com.google.common.annotations.VisibleForTesting" 446 ); 447 448 /** Name for 'public' access modifier. */ 449 private static final String PUBLIC_ACCESS_MODIFIER = "public"; 450 451 /** Name for 'private' access modifier. */ 452 private static final String PRIVATE_ACCESS_MODIFIER = "private"; 453 454 /** Name for 'protected' access modifier. */ 455 private static final String PROTECTED_ACCESS_MODIFIER = "protected"; 456 457 /** Name for implicit 'package' access modifier. */ 458 private static final String PACKAGE_ACCESS_MODIFIER = "package"; 459 460 /** Name for 'static' keyword. */ 461 private static final String STATIC_KEYWORD = "static"; 462 463 /** Name for 'final' keyword. */ 464 private static final String FINAL_KEYWORD = "final"; 465 466 /** Contains explicit access modifiers. */ 467 private static final String[] EXPLICIT_MODS = { 468 PUBLIC_ACCESS_MODIFIER, 469 PRIVATE_ACCESS_MODIFIER, 470 PROTECTED_ACCESS_MODIFIER, 471 }; 472 473 /** 474 * Specify pattern for public members that should be ignored. 475 */ 476 private Pattern publicMemberPattern = Pattern.compile("^serialVersionUID$"); 477 478 /** Set of ignore annotations short names. */ 479 private Set<String> ignoreAnnotationShortNames; 480 481 /** Set of immutable classes short names. */ 482 private Set<String> immutableClassShortNames; 483 484 /** 485 * Specify annotations canonical names which ignore variables in 486 * consideration. 487 */ 488 private Set<String> ignoreAnnotationCanonicalNames = DEFAULT_IGNORE_ANNOTATIONS; 489 490 /** Control whether protected members are allowed. */ 491 private boolean protectedAllowed; 492 493 /** Control whether package visible members are allowed. */ 494 private boolean packageAllowed; 495 496 /** Allow immutable fields to be declared as public if defined in final class. */ 497 private boolean allowPublicImmutableFields; 498 499 /** Allow final fields to be declared as public. */ 500 private boolean allowPublicFinalFields; 501 502 /** Specify immutable classes canonical names. */ 503 private Set<String> immutableClassCanonicalNames = DEFAULT_IMMUTABLE_TYPES; 504 505 /** 506 * Setter to specify annotations canonical names which ignore variables 507 * in consideration. 508 * 509 * @param annotationNames array of ignore annotations canonical names. 510 */ 511 public void setIgnoreAnnotationCanonicalNames(String... annotationNames) { 512 ignoreAnnotationCanonicalNames = Set.of(annotationNames); 513 } 514 515 /** 516 * Setter to control whether protected members are allowed. 517 * 518 * @param protectedAllowed whether protected members are allowed 519 */ 520 public void setProtectedAllowed(boolean protectedAllowed) { 521 this.protectedAllowed = protectedAllowed; 522 } 523 524 /** 525 * Setter to control whether package visible members are allowed. 526 * 527 * @param packageAllowed whether package visible members are allowed 528 */ 529 public void setPackageAllowed(boolean packageAllowed) { 530 this.packageAllowed = packageAllowed; 531 } 532 533 /** 534 * Setter to specify pattern for public members that should be ignored. 535 * 536 * @param pattern 537 * pattern for public members to ignore. 538 */ 539 public void setPublicMemberPattern(Pattern pattern) { 540 publicMemberPattern = pattern; 541 } 542 543 /** 544 * Setter to allow immutable fields to be declared as public if defined in final class. 545 * 546 * @param allow user's value. 547 */ 548 public void setAllowPublicImmutableFields(boolean allow) { 549 allowPublicImmutableFields = allow; 550 } 551 552 /** 553 * Setter to allow final fields to be declared as public. 554 * 555 * @param allow user's value. 556 */ 557 public void setAllowPublicFinalFields(boolean allow) { 558 allowPublicFinalFields = allow; 559 } 560 561 /** 562 * Setter to specify immutable classes canonical names. 563 * 564 * @param classNames array of immutable types canonical names. 565 */ 566 public void setImmutableClassCanonicalNames(String... classNames) { 567 immutableClassCanonicalNames = Set.of(classNames); 568 } 569 570 @Override 571 public int[] getDefaultTokens() { 572 return getRequiredTokens(); 573 } 574 575 @Override 576 public int[] getAcceptableTokens() { 577 return getRequiredTokens(); 578 } 579 580 @Override 581 public int[] getRequiredTokens() { 582 return new int[] { 583 TokenTypes.VARIABLE_DEF, 584 TokenTypes.IMPORT, 585 }; 586 } 587 588 @Override 589 public void beginTree(DetailAST rootAst) { 590 immutableClassShortNames = getClassShortNames(immutableClassCanonicalNames); 591 ignoreAnnotationShortNames = getClassShortNames(ignoreAnnotationCanonicalNames); 592 } 593 594 @Override 595 public void visitToken(DetailAST ast) { 596 switch (ast.getType()) { 597 case TokenTypes.VARIABLE_DEF: 598 if (!isAnonymousClassVariable(ast)) { 599 visitVariableDef(ast); 600 } 601 break; 602 case TokenTypes.IMPORT: 603 visitImport(ast); 604 break; 605 default: 606 final String exceptionMsg = "Unexpected token type: " + ast.getText(); 607 throw new IllegalArgumentException(exceptionMsg); 608 } 609 } 610 611 /** 612 * Checks if current variable definition is definition of an anonymous class. 613 * 614 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 615 * @return true if current variable definition is definition of an anonymous class. 616 */ 617 private static boolean isAnonymousClassVariable(DetailAST variableDef) { 618 return variableDef.getParent().getType() != TokenTypes.OBJBLOCK; 619 } 620 621 /** 622 * Checks access modifier of given variable. 623 * If it is not proper according to Check - puts violation on it. 624 * 625 * @param variableDef variable to check. 626 */ 627 private void visitVariableDef(DetailAST variableDef) { 628 final boolean inInterfaceOrAnnotationBlock = 629 ScopeUtil.isInInterfaceOrAnnotationBlock(variableDef); 630 631 if (!inInterfaceOrAnnotationBlock && !hasIgnoreAnnotation(variableDef)) { 632 final DetailAST varNameAST = variableDef.findFirstToken(TokenTypes.TYPE) 633 .getNextSibling(); 634 final String varName = varNameAST.getText(); 635 if (!hasProperAccessModifier(variableDef, varName)) { 636 log(varNameAST, MSG_KEY, varName); 637 } 638 } 639 } 640 641 /** 642 * Checks if variable def has ignore annotation. 643 * 644 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 645 * @return true if variable def has ignore annotation. 646 */ 647 private boolean hasIgnoreAnnotation(DetailAST variableDef) { 648 final DetailAST firstIgnoreAnnotation = 649 findMatchingAnnotation(variableDef); 650 return firstIgnoreAnnotation != null; 651 } 652 653 /** 654 * Checks imported type. If type's canonical name was not specified in 655 * <b>immutableClassCanonicalNames</b>, but its short name collides with one from 656 * <b>immutableClassShortNames</b> - removes it from the last one. 657 * 658 * @param importAst {@link TokenTypes#IMPORT Import} 659 */ 660 private void visitImport(DetailAST importAst) { 661 if (!isStarImport(importAst)) { 662 final String canonicalName = getCanonicalName(importAst); 663 final String shortName = getClassShortName(canonicalName); 664 665 // If imported canonical class name is not specified as allowed immutable class, 666 // but its short name collides with one of specified class - removes the short name 667 // from list to avoid names collision 668 if (!immutableClassCanonicalNames.contains(canonicalName)) { 669 immutableClassShortNames.remove(shortName); 670 } 671 if (!ignoreAnnotationCanonicalNames.contains(canonicalName)) { 672 ignoreAnnotationShortNames.remove(shortName); 673 } 674 } 675 } 676 677 /** 678 * Checks if current import is star import. E.g.: 679 * <p> 680 * {@code 681 * import java.util.*; 682 * } 683 * </p> 684 * 685 * @param importAst {@link TokenTypes#IMPORT Import} 686 * @return true if it is star import 687 */ 688 private static boolean isStarImport(DetailAST importAst) { 689 boolean result = false; 690 DetailAST toVisit = importAst; 691 while (toVisit != null) { 692 toVisit = getNextSubTreeNode(toVisit, importAst); 693 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) { 694 result = true; 695 break; 696 } 697 } 698 return result; 699 } 700 701 /** 702 * Checks if current variable has proper access modifier according to Check's options. 703 * 704 * @param variableDef Variable definition node. 705 * @param variableName Variable's name. 706 * @return true if variable has proper access modifier. 707 */ 708 private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) { 709 boolean result = true; 710 711 final String variableScope = getVisibilityScope(variableDef); 712 713 if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) { 714 result = 715 isStaticFinalVariable(variableDef) 716 || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope) 717 || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope) 718 || isIgnoredPublicMember(variableName, variableScope) 719 || isAllowedPublicField(variableDef); 720 } 721 722 return result; 723 } 724 725 /** 726 * Checks whether variable has static final modifiers. 727 * 728 * @param variableDef Variable definition node. 729 * @return true of variable has static final modifiers. 730 */ 731 private static boolean isStaticFinalVariable(DetailAST variableDef) { 732 final Set<String> modifiers = getModifiers(variableDef); 733 return modifiers.contains(STATIC_KEYWORD) 734 && modifiers.contains(FINAL_KEYWORD); 735 } 736 737 /** 738 * Checks whether variable belongs to public members that should be ignored. 739 * 740 * @param variableName Variable's name. 741 * @param variableScope Variable's scope. 742 * @return true if variable belongs to public members that should be ignored. 743 */ 744 private boolean isIgnoredPublicMember(String variableName, String variableScope) { 745 return PUBLIC_ACCESS_MODIFIER.equals(variableScope) 746 && publicMemberPattern.matcher(variableName).find(); 747 } 748 749 /** 750 * Checks whether the variable satisfies the public field check. 751 * 752 * @param variableDef Variable definition node. 753 * @return true if allowed. 754 */ 755 private boolean isAllowedPublicField(DetailAST variableDef) { 756 return allowPublicFinalFields && isFinalField(variableDef) 757 || allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef); 758 } 759 760 /** 761 * Checks whether immutable field is defined in final class. 762 * 763 * @param variableDef Variable definition node. 764 * @return true if immutable field is defined in final class. 765 */ 766 private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) { 767 final DetailAST classDef = variableDef.getParent().getParent(); 768 final Set<String> classModifiers = getModifiers(classDef); 769 return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF) 770 && isImmutableField(variableDef); 771 } 772 773 /** 774 * Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST. 775 * 776 * @param defAST AST for a variable or class definition. 777 * @return the set of modifier Strings for defAST. 778 */ 779 private static Set<String> getModifiers(DetailAST defAST) { 780 final DetailAST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS); 781 final Set<String> modifiersSet = new HashSet<>(); 782 if (modifiersAST != null) { 783 DetailAST modifier = modifiersAST.getFirstChild(); 784 while (modifier != null) { 785 modifiersSet.add(modifier.getText()); 786 modifier = modifier.getNextSibling(); 787 } 788 } 789 return modifiersSet; 790 } 791 792 /** 793 * Returns the visibility scope for the variable. 794 * 795 * @param variableDef Variable definition node. 796 * @return one of "public", "private", "protected", "package" 797 */ 798 private static String getVisibilityScope(DetailAST variableDef) { 799 final Set<String> modifiers = getModifiers(variableDef); 800 String accessModifier = PACKAGE_ACCESS_MODIFIER; 801 for (final String modifier : EXPLICIT_MODS) { 802 if (modifiers.contains(modifier)) { 803 accessModifier = modifier; 804 break; 805 } 806 } 807 return accessModifier; 808 } 809 810 /** 811 * Checks if current field is immutable: 812 * has final modifier and either a primitive type or instance of class 813 * known to be immutable (such as String, ImmutableCollection from Guava, etc.). 814 * Classes known to be immutable are listed in 815 * {@link VisibilityModifierCheck#immutableClassCanonicalNames} 816 * 817 * @param variableDef Field in consideration. 818 * @return true if field is immutable. 819 */ 820 private boolean isImmutableField(DetailAST variableDef) { 821 boolean result = false; 822 if (isFinalField(variableDef)) { 823 final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE); 824 final boolean isCanonicalName = isCanonicalName(type); 825 final String typeName = getCanonicalName(type); 826 if (immutableClassShortNames.contains(typeName) 827 || isCanonicalName && immutableClassCanonicalNames.contains(typeName)) { 828 final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName); 829 830 if (typeArgs == null) { 831 result = true; 832 } 833 else { 834 final List<String> argsClassNames = getTypeArgsClassNames(typeArgs); 835 result = areImmutableTypeArguments(argsClassNames); 836 } 837 } 838 else { 839 result = !isCanonicalName && isPrimitive(type); 840 } 841 } 842 return result; 843 } 844 845 /** 846 * Checks whether type definition is in canonical form. 847 * 848 * @param type type definition token. 849 * @return true if type definition is in canonical form. 850 */ 851 private static boolean isCanonicalName(DetailAST type) { 852 return type.getFirstChild().getType() == TokenTypes.DOT; 853 } 854 855 /** 856 * Returns generic type arguments token. 857 * 858 * @param type type token. 859 * @param isCanonicalName whether type name is in canonical form. 860 * @return generic type arguments token. 861 */ 862 private static DetailAST getGenericTypeArgs(DetailAST type, boolean isCanonicalName) { 863 final DetailAST typeArgs; 864 if (isCanonicalName) { 865 // if type class name is in canonical form, abstract tree has specific structure 866 typeArgs = type.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS); 867 } 868 else { 869 typeArgs = type.findFirstToken(TokenTypes.TYPE_ARGUMENTS); 870 } 871 return typeArgs; 872 } 873 874 /** 875 * Returns a list of type parameters class names. 876 * 877 * @param typeArgs type arguments token. 878 * @return a list of type parameters class names. 879 */ 880 private static List<String> getTypeArgsClassNames(DetailAST typeArgs) { 881 final List<String> typeClassNames = new ArrayList<>(); 882 DetailAST type = typeArgs.findFirstToken(TokenTypes.TYPE_ARGUMENT); 883 DetailAST sibling; 884 do { 885 final String typeName = getCanonicalName(type); 886 typeClassNames.add(typeName); 887 sibling = type.getNextSibling(); 888 type = sibling.getNextSibling(); 889 } while (sibling.getType() == TokenTypes.COMMA); 890 return typeClassNames; 891 } 892 893 /** 894 * Checks whether all generic type arguments are immutable. 895 * If at least one argument is mutable, we assume that the whole list of type arguments 896 * is mutable. 897 * 898 * @param typeArgsClassNames type arguments class names. 899 * @return true if all generic type arguments are immutable. 900 */ 901 private boolean areImmutableTypeArguments(Collection<String> typeArgsClassNames) { 902 return typeArgsClassNames.stream().noneMatch( 903 typeName -> { 904 return !immutableClassShortNames.contains(typeName) 905 && !immutableClassCanonicalNames.contains(typeName); 906 }); 907 } 908 909 /** 910 * Checks whether current field is final. 911 * 912 * @param variableDef field in consideration. 913 * @return true if current field is final. 914 */ 915 private static boolean isFinalField(DetailAST variableDef) { 916 final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS); 917 return modifiers.findFirstToken(TokenTypes.FINAL) != null; 918 } 919 920 /** 921 * Checks if current type is primitive type (int, short, float, boolean, double, etc.). 922 * As primitive types have special tokens for each one, such as: 923 * LITERAL_INT, LITERAL_BOOLEAN, etc. 924 * So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a 925 * primitive type. 926 * 927 * @param type Ast {@link TokenTypes#TYPE TYPE} node. 928 * @return true if current type is primitive type. 929 */ 930 private static boolean isPrimitive(DetailAST type) { 931 return type.getFirstChild().getType() != TokenTypes.IDENT; 932 } 933 934 /** 935 * Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node. 936 * 937 * @param type DetailAST {@link TokenTypes#TYPE TYPE} node. 938 * @return canonical type's name 939 */ 940 private static String getCanonicalName(DetailAST type) { 941 final StringBuilder canonicalNameBuilder = new StringBuilder(256); 942 DetailAST toVisit = type; 943 while (toVisit != null) { 944 toVisit = getNextSubTreeNode(toVisit, type); 945 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) { 946 if (canonicalNameBuilder.length() > 0) { 947 canonicalNameBuilder.append('.'); 948 } 949 canonicalNameBuilder.append(toVisit.getText()); 950 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type); 951 if (nextSubTreeNode != null 952 && nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) { 953 break; 954 } 955 } 956 } 957 return canonicalNameBuilder.toString(); 958 } 959 960 /** 961 * Gets the next node of a syntactical tree (child of a current node or 962 * sibling of a current node, or sibling of a parent of a current node). 963 * 964 * @param currentNodeAst Current node in considering 965 * @param subTreeRootAst SubTree root 966 * @return Current node after bypassing, if current node reached the root of a subtree 967 * method returns null 968 */ 969 private static DetailAST 970 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) { 971 DetailAST currentNode = currentNodeAst; 972 DetailAST toVisitAst = currentNode.getFirstChild(); 973 while (toVisitAst == null) { 974 toVisitAst = currentNode.getNextSibling(); 975 if (currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) { 976 break; 977 } 978 currentNode = currentNode.getParent(); 979 } 980 return toVisitAst; 981 } 982 983 /** 984 * Converts canonical class names to short names. 985 * 986 * @param canonicalClassNames the set of canonical class names. 987 * @return the set of short names of classes. 988 */ 989 private static Set<String> getClassShortNames(Set<String> canonicalClassNames) { 990 return canonicalClassNames.stream() 991 .map(CommonUtil::baseClassName) 992 .collect(Collectors.toCollection(HashSet::new)); 993 } 994 995 /** 996 * Gets the short class name from given canonical name. 997 * 998 * @param canonicalClassName canonical class name. 999 * @return short name of class. 1000 */ 1001 private static String getClassShortName(String canonicalClassName) { 1002 return canonicalClassName 1003 .substring(canonicalClassName.lastIndexOf('.') + 1); 1004 } 1005 1006 /** 1007 * Checks whether the AST is annotated with 1008 * an annotation containing the passed in regular 1009 * expression and return the AST representing that 1010 * annotation. 1011 * 1012 * <p> 1013 * This method will not look for imports or package 1014 * statements to detect the passed in annotation. 1015 * </p> 1016 * 1017 * <p> 1018 * To check if an AST contains a passed in annotation 1019 * taking into account fully-qualified names 1020 * (ex: java.lang.Override, Override) 1021 * this method will need to be called twice. Once for each 1022 * name given. 1023 * </p> 1024 * 1025 * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}. 1026 * @return the AST representing the first such annotation or null if 1027 * no such annotation was found 1028 */ 1029 private DetailAST findMatchingAnnotation(DetailAST variableDef) { 1030 DetailAST matchingAnnotation = null; 1031 1032 final DetailAST holder = AnnotationUtil.getAnnotationHolder(variableDef); 1033 1034 for (DetailAST child = holder.getFirstChild(); 1035 child != null; child = child.getNextSibling()) { 1036 if (child.getType() == TokenTypes.ANNOTATION) { 1037 final DetailAST ast = child.getFirstChild(); 1038 final String name = 1039 FullIdent.createFullIdent(ast.getNextSibling()).getText(); 1040 if (ignoreAnnotationCanonicalNames.contains(name) 1041 || ignoreAnnotationShortNames.contains(name)) { 1042 matchingAnnotation = child; 1043 break; 1044 } 1045 } 1046 } 1047 1048 return matchingAnnotation; 1049 } 1050 1051}