001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2022 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 getClassShortNames(DEFAULT_IGNORE_ANNOTATIONS); 481 482 /** Set of immutable classes short names. */ 483 private Set<String> immutableClassShortNames = 484 getClassShortNames(DEFAULT_IMMUTABLE_TYPES); 485 486 /** 487 * Specify annotations canonical names which ignore variables in 488 * consideration. 489 */ 490 private Set<String> ignoreAnnotationCanonicalNames = DEFAULT_IGNORE_ANNOTATIONS; 491 492 /** Control whether protected members are allowed. */ 493 private boolean protectedAllowed; 494 495 /** Control whether package visible members are allowed. */ 496 private boolean packageAllowed; 497 498 /** Allow immutable fields to be declared as public if defined in final class. */ 499 private boolean allowPublicImmutableFields; 500 501 /** Allow final fields to be declared as public. */ 502 private boolean allowPublicFinalFields; 503 504 /** Specify immutable classes canonical names. */ 505 private Set<String> immutableClassCanonicalNames = DEFAULT_IMMUTABLE_TYPES; 506 507 /** 508 * Setter to specify annotations canonical names which ignore variables 509 * in consideration. 510 * 511 * @param annotationNames array of ignore annotations canonical names. 512 */ 513 public void setIgnoreAnnotationCanonicalNames(String... annotationNames) { 514 ignoreAnnotationCanonicalNames = Set.of(annotationNames); 515 } 516 517 /** 518 * Setter to control whether protected members are allowed. 519 * 520 * @param protectedAllowed whether protected members are allowed 521 */ 522 public void setProtectedAllowed(boolean protectedAllowed) { 523 this.protectedAllowed = protectedAllowed; 524 } 525 526 /** 527 * Setter to control whether package visible members are allowed. 528 * 529 * @param packageAllowed whether package visible members are allowed 530 */ 531 public void setPackageAllowed(boolean packageAllowed) { 532 this.packageAllowed = packageAllowed; 533 } 534 535 /** 536 * Setter to specify pattern for public members that should be ignored. 537 * 538 * @param pattern 539 * pattern for public members to ignore. 540 */ 541 public void setPublicMemberPattern(Pattern pattern) { 542 publicMemberPattern = pattern; 543 } 544 545 /** 546 * Setter to allow immutable fields to be declared as public if defined in final class. 547 * 548 * @param allow user's value. 549 */ 550 public void setAllowPublicImmutableFields(boolean allow) { 551 allowPublicImmutableFields = allow; 552 } 553 554 /** 555 * Setter to allow final fields to be declared as public. 556 * 557 * @param allow user's value. 558 */ 559 public void setAllowPublicFinalFields(boolean allow) { 560 allowPublicFinalFields = allow; 561 } 562 563 /** 564 * Setter to specify immutable classes canonical names. 565 * 566 * @param classNames array of immutable types canonical names. 567 */ 568 public void setImmutableClassCanonicalNames(String... classNames) { 569 immutableClassCanonicalNames = Set.of(classNames); 570 } 571 572 @Override 573 public int[] getDefaultTokens() { 574 return getRequiredTokens(); 575 } 576 577 @Override 578 public int[] getAcceptableTokens() { 579 return getRequiredTokens(); 580 } 581 582 @Override 583 public int[] getRequiredTokens() { 584 return new int[] { 585 TokenTypes.VARIABLE_DEF, 586 TokenTypes.IMPORT, 587 }; 588 } 589 590 @Override 591 public void beginTree(DetailAST rootAst) { 592 immutableClassShortNames = getClassShortNames(immutableClassCanonicalNames); 593 ignoreAnnotationShortNames = getClassShortNames(ignoreAnnotationCanonicalNames); 594 } 595 596 @Override 597 public void visitToken(DetailAST ast) { 598 switch (ast.getType()) { 599 case TokenTypes.VARIABLE_DEF: 600 if (!isAnonymousClassVariable(ast)) { 601 visitVariableDef(ast); 602 } 603 break; 604 case TokenTypes.IMPORT: 605 visitImport(ast); 606 break; 607 default: 608 final String exceptionMsg = "Unexpected token type: " + ast.getText(); 609 throw new IllegalArgumentException(exceptionMsg); 610 } 611 } 612 613 /** 614 * Checks if current variable definition is definition of an anonymous class. 615 * 616 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 617 * @return true if current variable definition is definition of an anonymous class. 618 */ 619 private static boolean isAnonymousClassVariable(DetailAST variableDef) { 620 return variableDef.getParent().getType() != TokenTypes.OBJBLOCK; 621 } 622 623 /** 624 * Checks access modifier of given variable. 625 * If it is not proper according to Check - puts violation on it. 626 * 627 * @param variableDef variable to check. 628 */ 629 private void visitVariableDef(DetailAST variableDef) { 630 final boolean inInterfaceOrAnnotationBlock = 631 ScopeUtil.isInInterfaceOrAnnotationBlock(variableDef); 632 633 if (!inInterfaceOrAnnotationBlock && !hasIgnoreAnnotation(variableDef)) { 634 final DetailAST varNameAST = variableDef.findFirstToken(TokenTypes.TYPE) 635 .getNextSibling(); 636 final String varName = varNameAST.getText(); 637 if (!hasProperAccessModifier(variableDef, varName)) { 638 log(varNameAST, MSG_KEY, varName); 639 } 640 } 641 } 642 643 /** 644 * Checks if variable def has ignore annotation. 645 * 646 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 647 * @return true if variable def has ignore annotation. 648 */ 649 private boolean hasIgnoreAnnotation(DetailAST variableDef) { 650 final DetailAST firstIgnoreAnnotation = 651 findMatchingAnnotation(variableDef); 652 return firstIgnoreAnnotation != null; 653 } 654 655 /** 656 * Checks imported type. If type's canonical name was not specified in 657 * <b>immutableClassCanonicalNames</b>, but its short name collides with one from 658 * <b>immutableClassShortNames</b> - removes it from the last one. 659 * 660 * @param importAst {@link TokenTypes#IMPORT Import} 661 */ 662 private void visitImport(DetailAST importAst) { 663 if (!isStarImport(importAst)) { 664 final DetailAST type = importAst.getFirstChild(); 665 final String canonicalName = getCanonicalName(type); 666 final String shortName = getClassShortName(canonicalName); 667 668 // If imported canonical class name is not specified as allowed immutable class, 669 // but its short name collides with one of specified class - removes the short name 670 // from list to avoid names collision 671 if (!immutableClassCanonicalNames.contains(canonicalName)) { 672 immutableClassShortNames.remove(shortName); 673 } 674 if (!ignoreAnnotationCanonicalNames.contains(canonicalName)) { 675 ignoreAnnotationShortNames.remove(shortName); 676 } 677 } 678 } 679 680 /** 681 * Checks if current import is star import. E.g.: 682 * <p> 683 * {@code 684 * import java.util.*; 685 * } 686 * </p> 687 * 688 * @param importAst {@link TokenTypes#IMPORT Import} 689 * @return true if it is star import 690 */ 691 private static boolean isStarImport(DetailAST importAst) { 692 boolean result = false; 693 DetailAST toVisit = importAst; 694 while (toVisit != null) { 695 toVisit = getNextSubTreeNode(toVisit, importAst); 696 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) { 697 result = true; 698 break; 699 } 700 } 701 return result; 702 } 703 704 /** 705 * Checks if current variable has proper access modifier according to Check's options. 706 * 707 * @param variableDef Variable definition node. 708 * @param variableName Variable's name. 709 * @return true if variable has proper access modifier. 710 */ 711 private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) { 712 boolean result = true; 713 714 final String variableScope = getVisibilityScope(variableDef); 715 716 if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) { 717 result = 718 isStaticFinalVariable(variableDef) 719 || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope) 720 || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope) 721 || isIgnoredPublicMember(variableName, variableScope) 722 || isAllowedPublicField(variableDef); 723 } 724 725 return result; 726 } 727 728 /** 729 * Checks whether variable has static final modifiers. 730 * 731 * @param variableDef Variable definition node. 732 * @return true of variable has static final modifiers. 733 */ 734 private static boolean isStaticFinalVariable(DetailAST variableDef) { 735 final Set<String> modifiers = getModifiers(variableDef); 736 return modifiers.contains(STATIC_KEYWORD) 737 && modifiers.contains(FINAL_KEYWORD); 738 } 739 740 /** 741 * Checks whether variable belongs to public members that should be ignored. 742 * 743 * @param variableName Variable's name. 744 * @param variableScope Variable's scope. 745 * @return true if variable belongs to public members that should be ignored. 746 */ 747 private boolean isIgnoredPublicMember(String variableName, String variableScope) { 748 return PUBLIC_ACCESS_MODIFIER.equals(variableScope) 749 && publicMemberPattern.matcher(variableName).find(); 750 } 751 752 /** 753 * Checks whether the variable satisfies the public field check. 754 * 755 * @param variableDef Variable definition node. 756 * @return true if allowed. 757 */ 758 private boolean isAllowedPublicField(DetailAST variableDef) { 759 return allowPublicFinalFields && isFinalField(variableDef) 760 || allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef); 761 } 762 763 /** 764 * Checks whether immutable field is defined in final class. 765 * 766 * @param variableDef Variable definition node. 767 * @return true if immutable field is defined in final class. 768 */ 769 private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) { 770 final DetailAST classDef = variableDef.getParent().getParent(); 771 final Set<String> classModifiers = getModifiers(classDef); 772 return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF) 773 && isImmutableField(variableDef); 774 } 775 776 /** 777 * Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST. 778 * 779 * @param defAST AST for a variable or class definition. 780 * @return the set of modifier Strings for defAST. 781 */ 782 private static Set<String> getModifiers(DetailAST defAST) { 783 final DetailAST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS); 784 final Set<String> modifiersSet = new HashSet<>(); 785 if (modifiersAST != null) { 786 DetailAST modifier = modifiersAST.getFirstChild(); 787 while (modifier != null) { 788 modifiersSet.add(modifier.getText()); 789 modifier = modifier.getNextSibling(); 790 } 791 } 792 return modifiersSet; 793 } 794 795 /** 796 * Returns the visibility scope for the variable. 797 * 798 * @param variableDef Variable definition node. 799 * @return one of "public", "private", "protected", "package" 800 */ 801 private static String getVisibilityScope(DetailAST variableDef) { 802 final Set<String> modifiers = getModifiers(variableDef); 803 String accessModifier = PACKAGE_ACCESS_MODIFIER; 804 for (final String modifier : EXPLICIT_MODS) { 805 if (modifiers.contains(modifier)) { 806 accessModifier = modifier; 807 break; 808 } 809 } 810 return accessModifier; 811 } 812 813 /** 814 * Checks if current field is immutable: 815 * has final modifier and either a primitive type or instance of class 816 * known to be immutable (such as String, ImmutableCollection from Guava, etc.). 817 * Classes known to be immutable are listed in 818 * {@link VisibilityModifierCheck#immutableClassCanonicalNames} 819 * 820 * @param variableDef Field in consideration. 821 * @return true if field is immutable. 822 */ 823 private boolean isImmutableField(DetailAST variableDef) { 824 boolean result = false; 825 if (isFinalField(variableDef)) { 826 final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE); 827 final boolean isCanonicalName = isCanonicalName(type); 828 final String typeName = getTypeName(type, isCanonicalName); 829 if (immutableClassShortNames.contains(typeName) 830 || isCanonicalName && immutableClassCanonicalNames.contains(typeName)) { 831 final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName); 832 833 if (typeArgs == null) { 834 result = true; 835 } 836 else { 837 final List<String> argsClassNames = getTypeArgsClassNames(typeArgs); 838 result = areImmutableTypeArguments(argsClassNames); 839 } 840 } 841 else { 842 result = !isCanonicalName && isPrimitive(type); 843 } 844 } 845 return result; 846 } 847 848 /** 849 * Checks whether type definition is in canonical form. 850 * 851 * @param type type definition token. 852 * @return true if type definition is in canonical form. 853 */ 854 private static boolean isCanonicalName(DetailAST type) { 855 return type.getFirstChild().getType() == TokenTypes.DOT; 856 } 857 858 /** 859 * Returns generic type arguments token. 860 * 861 * @param type type token. 862 * @param isCanonicalName whether type name is in canonical form. 863 * @return generic type arguments token. 864 */ 865 private static DetailAST getGenericTypeArgs(DetailAST type, boolean isCanonicalName) { 866 final DetailAST typeArgs; 867 if (isCanonicalName) { 868 // if type class name is in canonical form, abstract tree has specific structure 869 typeArgs = type.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS); 870 } 871 else { 872 typeArgs = type.findFirstToken(TokenTypes.TYPE_ARGUMENTS); 873 } 874 return typeArgs; 875 } 876 877 /** 878 * Returns a list of type parameters class names. 879 * 880 * @param typeArgs type arguments token. 881 * @return a list of type parameters class names. 882 */ 883 private static List<String> getTypeArgsClassNames(DetailAST typeArgs) { 884 final List<String> typeClassNames = new ArrayList<>(); 885 DetailAST type = typeArgs.findFirstToken(TokenTypes.TYPE_ARGUMENT); 886 DetailAST sibling; 887 do { 888 final boolean isCanonicalName = isCanonicalName(type); 889 final String typeName = getTypeName(type, isCanonicalName); 890 typeClassNames.add(typeName); 891 sibling = type.getNextSibling(); 892 type = sibling.getNextSibling(); 893 } while (sibling.getType() == TokenTypes.COMMA); 894 return typeClassNames; 895 } 896 897 /** 898 * Checks whether all generic type arguments are immutable. 899 * If at least one argument is mutable, we assume that the whole list of type arguments 900 * is mutable. 901 * 902 * @param typeArgsClassNames type arguments class names. 903 * @return true if all generic type arguments are immutable. 904 */ 905 private boolean areImmutableTypeArguments(Collection<String> typeArgsClassNames) { 906 return typeArgsClassNames.stream().noneMatch( 907 typeName -> { 908 return !immutableClassShortNames.contains(typeName) 909 && !immutableClassCanonicalNames.contains(typeName); 910 }); 911 } 912 913 /** 914 * Checks whether current field is final. 915 * 916 * @param variableDef field in consideration. 917 * @return true if current field is final. 918 */ 919 private static boolean isFinalField(DetailAST variableDef) { 920 final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS); 921 return modifiers.findFirstToken(TokenTypes.FINAL) != null; 922 } 923 924 /** 925 * Gets the name of type from given ast {@link TokenTypes#TYPE TYPE} node. 926 * If type is specified via its canonical name - canonical name will be returned, 927 * else - short type's name. 928 * 929 * @param type {@link TokenTypes#TYPE TYPE} node. 930 * @param isCanonicalName is given name canonical. 931 * @return String representation of given type's name. 932 */ 933 private static String getTypeName(DetailAST type, boolean isCanonicalName) { 934 final String typeName; 935 if (isCanonicalName) { 936 typeName = getCanonicalName(type); 937 } 938 else { 939 typeName = type.getFirstChild().getText(); 940 } 941 return typeName; 942 } 943 944 /** 945 * Checks if current type is primitive type (int, short, float, boolean, double, etc.). 946 * As primitive types have special tokens for each one, such as: 947 * LITERAL_INT, LITERAL_BOOLEAN, etc. 948 * So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a 949 * primitive type. 950 * 951 * @param type Ast {@link TokenTypes#TYPE TYPE} node. 952 * @return true if current type is primitive type. 953 */ 954 private static boolean isPrimitive(DetailAST type) { 955 return type.getFirstChild().getType() != TokenTypes.IDENT; 956 } 957 958 /** 959 * Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node. 960 * 961 * @param type DetailAST {@link TokenTypes#TYPE TYPE} node. 962 * @return canonical type's name 963 */ 964 private static String getCanonicalName(DetailAST type) { 965 final StringBuilder canonicalNameBuilder = new StringBuilder(256); 966 DetailAST toVisit = type.getFirstChild(); 967 while (toVisit != null) { 968 toVisit = getNextSubTreeNode(toVisit, type); 969 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) { 970 if (canonicalNameBuilder.length() > 0) { 971 canonicalNameBuilder.append('.'); 972 } 973 canonicalNameBuilder.append(toVisit.getText()); 974 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type); 975 if (nextSubTreeNode != null 976 && nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) { 977 break; 978 } 979 } 980 } 981 return canonicalNameBuilder.toString(); 982 } 983 984 /** 985 * Gets the next node of a syntactical tree (child of a current node or 986 * sibling of a current node, or sibling of a parent of a current node). 987 * 988 * @param currentNodeAst Current node in considering 989 * @param subTreeRootAst SubTree root 990 * @return Current node after bypassing, if current node reached the root of a subtree 991 * method returns null 992 */ 993 private static DetailAST 994 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) { 995 DetailAST currentNode = currentNodeAst; 996 DetailAST toVisitAst = currentNode.getFirstChild(); 997 while (toVisitAst == null) { 998 toVisitAst = currentNode.getNextSibling(); 999 if (currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) { 1000 break; 1001 } 1002 currentNode = currentNode.getParent(); 1003 } 1004 return toVisitAst; 1005 } 1006 1007 /** 1008 * Converts canonical class names to short names. 1009 * 1010 * @param canonicalClassNames the set of canonical class names. 1011 * @return the set of short names of classes. 1012 */ 1013 private static Set<String> getClassShortNames(Set<String> canonicalClassNames) { 1014 return canonicalClassNames.stream() 1015 .map(CommonUtil::baseClassName) 1016 .collect(Collectors.toCollection(HashSet::new)); 1017 } 1018 1019 /** 1020 * Gets the short class name from given canonical name. 1021 * 1022 * @param canonicalClassName canonical class name. 1023 * @return short name of class. 1024 */ 1025 private static String getClassShortName(String canonicalClassName) { 1026 return canonicalClassName 1027 .substring(canonicalClassName.lastIndexOf('.') + 1); 1028 } 1029 1030 /** 1031 * Checks whether the AST is annotated with 1032 * an annotation containing the passed in regular 1033 * expression and return the AST representing that 1034 * annotation. 1035 * 1036 * <p> 1037 * This method will not look for imports or package 1038 * statements to detect the passed in annotation. 1039 * </p> 1040 * 1041 * <p> 1042 * To check if an AST contains a passed in annotation 1043 * taking into account fully-qualified names 1044 * (ex: java.lang.Override, Override) 1045 * this method will need to be called twice. Once for each 1046 * name given. 1047 * </p> 1048 * 1049 * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}. 1050 * @return the AST representing the first such annotation or null if 1051 * no such annotation was found 1052 */ 1053 private DetailAST findMatchingAnnotation(DetailAST variableDef) { 1054 DetailAST matchingAnnotation = null; 1055 1056 final DetailAST holder = AnnotationUtil.getAnnotationHolder(variableDef); 1057 1058 for (DetailAST child = holder.getFirstChild(); 1059 child != null; child = child.getNextSibling()) { 1060 if (child.getType() == TokenTypes.ANNOTATION) { 1061 final DetailAST ast = child.getFirstChild(); 1062 final String name = 1063 FullIdent.createFullIdent(ast.getNextSibling()).getText(); 1064 if (ignoreAnnotationCanonicalNames.contains(name) 1065 || ignoreAnnotationShortNames.contains(name)) { 1066 matchingAnnotation = child; 1067 break; 1068 } 1069 } 1070 } 1071 1072 return matchingAnnotation; 1073 } 1074 1075}