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