Coverage Report - com.puppycrawl.tools.checkstyle.checks.design.VisibilityModifierCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
VisibilityModifierCheck
100%
207/207
100%
143/143
0
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.design;
 21  
 
 22  
 import java.util.ArrayList;
 23  
 import java.util.Arrays;
 24  
 import java.util.Collections;
 25  
 import java.util.HashSet;
 26  
 import java.util.List;
 27  
 import java.util.Set;
 28  
 import java.util.regex.Pattern;
 29  
 import java.util.stream.Collectors;
 30  
 
 31  
 import antlr.collections.AST;
 32  
 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
 33  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 34  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 35  
 import com.puppycrawl.tools.checkstyle.api.FullIdent;
 36  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 37  
 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility;
 38  
 import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
 39  
 
 40  
 /**
 41  
  * Checks visibility of class members. Only static final, immutable or annotated
 42  
  * by specified annotation members may be public,
 43  
  * other class members must be private unless allowProtected/Package is set.
 44  
  * <p>
 45  
  * Public members are not flagged if the name matches the public
 46  
  * member regular expression (contains "^serialVersionUID$" by
 47  
  * default).
 48  
  * </p>
 49  
  * Rationale: Enforce encapsulation.
 50  
  * <p>
 51  
  * Check also has options making it less strict:
 52  
  * </p>
 53  
  * <p>
 54  
  * <b>ignoreAnnotationCanonicalNames</b> - the list of annotations canonical names
 55  
  * which ignore variables in consideration, if user will provide short annotation name
 56  
  * that type will match to any named the same type without consideration of package,
 57  
  * list by default:
 58  
  * </p>
 59  
  * <ul>
 60  
  * <li>org.junit.Rule</li>
 61  
  * <li>org.junit.ClassRule</li>
 62  
  * <li>com.google.common.annotations.VisibleForTesting</li>
 63  
  * </ul>
 64  
  * <p>
 65  
  * For example such public field will be skipped by default value of list above:
 66  
  * </p>
 67  
  *
 68  
  * <pre>
 69  
  * {@code @org.junit.Rule
 70  
  * public TemporaryFolder publicJUnitRule = new TemporaryFolder();
 71  
  * }
 72  
  * </pre>
 73  
  *
 74  
  * <p>
 75  
  * <b>allowPublicFinalFields</b> - which allows public final fields. Default value is <b>false</b>.
 76  
  * </p>
 77  
  * <p>
 78  
  * <b>allowPublicImmutableFields</b> - which allows immutable fields to be
 79  
  * declared as public if defined in final class. Default value is <b>false</b>
 80  
  * </p>
 81  
  * <p>
 82  
  * Field is known to be immutable if:
 83  
  * </p>
 84  
  * <ul>
 85  
  * <li>It's declared as final</li>
 86  
  * <li>Has either a primitive type or instance of class user defined to be immutable
 87  
  * (such as String, ImmutableCollection from Guava and etc)</li>
 88  
  * </ul>
 89  
  * <p>
 90  
  * Classes known to be immutable are listed in <b>immutableClassCanonicalNames</b> by their
 91  
  * <b>canonical</b> names. List by default:
 92  
  * </p>
 93  
  * <ul>
 94  
  * <li>java.lang.String</li>
 95  
  * <li>java.lang.Integer</li>
 96  
  * <li>java.lang.Byte</li>
 97  
  * <li>java.lang.Character</li>
 98  
  * <li>java.lang.Short</li>
 99  
  * <li>java.lang.Boolean</li>
 100  
  * <li>java.lang.Long</li>
 101  
  * <li>java.lang.Double</li>
 102  
  * <li>java.lang.Float</li>
 103  
  * <li>java.lang.StackTraceElement</li>
 104  
  * <li>java.lang.BigInteger</li>
 105  
  * <li>java.lang.BigDecimal</li>
 106  
  * <li>java.io.File</li>
 107  
  * <li>java.util.Locale</li>
 108  
  * <li>java.util.UUID</li>
 109  
  * <li>java.net.URL</li>
 110  
  * <li>java.net.URI</li>
 111  
  * <li>java.net.Inet4Address</li>
 112  
  * <li>java.net.Inet6Address</li>
 113  
  * <li>java.net.InetSocketAddress</li>
 114  
  * </ul>
 115  
  * <p>
 116  
  * User can override this list via adding <b>canonical</b> class names to
 117  
  * <b>immutableClassCanonicalNames</b>, if user will provide short class name all
 118  
  * that type will match to any named the same type without consideration of package.
 119  
  * </p>
 120  
  * <p>
 121  
  * <b>Rationale</b>: Forcing all fields of class to have private modified by default is good
 122  
  * in most cases, but in some cases it drawbacks in too much boilerplate get/set code.
 123  
  * One of such cases are immutable classes.
 124  
  * </p>
 125  
  * <p>
 126  
  * <b>Restriction</b>: Check doesn't check if class is immutable, there's no checking
 127  
  * if accessory methods are missing and all fields are immutable, we only check
 128  
  * <b>if current field is immutable by matching a name to user defined list of immutable classes
 129  
  * and defined in final class</b>
 130  
  * </p>
 131  
  * <p>
 132  
  * Star imports are out of scope of this Check. So if one of type imported via <b>star import</b>
 133  
  * collides with user specified one by its short name - there won't be Check's violation.
 134  
  * </p>
 135  
  * Examples:
 136  
  * <p>
 137  
  * The check will rise 3 violations if it is run with default configuration against the following
 138  
  * code example:
 139  
  * </p>
 140  
  *
 141  
  * <pre>
 142  
  * {@code
 143  
  * public class ImmutableClass
 144  
  * {
 145  
  *     public int intValue; // violation
 146  
  *     public java.lang.String notes; // violation
 147  
  *     public BigDecimal value; // violation
 148  
  *
 149  
  *     public ImmutableClass(int intValue, BigDecimal value, String notes)
 150  
  *     {
 151  
  *         this.intValue = intValue;
 152  
  *         this.value = value;
 153  
  *         this.notes = notes;
 154  
  *     }
 155  
  * }
 156  
  * }
 157  
  * </pre>
 158  
  *
 159  
  * <p>
 160  
  * To configure the Check passing fields of type com.google.common.collect.ImmutableSet and
 161  
  * java.util.List:
 162  
  * </p>
 163  
  * <p>
 164  
  * &lt;module name=&quot;VisibilityModifier&quot;&gt;
 165  
  *   &lt;property name=&quot;allowPublicImmutableFields&quot; value=&quot;true&quot;/&gt;
 166  
  *   &lt;property name=&quot;immutableClassCanonicalNames&quot; value=&quot;java.util.List,
 167  
  *   com.google.common.collect.ImmutableSet&quot;/&gt;
 168  
  * &lt;/module&gt;
 169  
  * </p>
 170  
  *
 171  
  * <pre>
 172  
  * {@code
 173  
  * public final class ImmutableClass
 174  
  * {
 175  
  *     public final ImmutableSet&lt;String&gt; includes; // No warning
 176  
  *     public final ImmutableSet&lt;String&gt; excludes; // No warning
 177  
  *     public final BigDecimal value; // Warning here, type BigDecimal isn't specified as immutable
 178  
  *
 179  
  *     public ImmutableClass(Collection&lt;String&gt; includes, Collection&lt;String&gt; excludes,
 180  
  *                  BigDecimal value)
 181  
  *     {
 182  
  *         this.includes = ImmutableSet.copyOf(includes);
 183  
  *         this.excludes = ImmutableSet.copyOf(excludes);
 184  
  *         this.value = value;
 185  
  *         this.notes = notes;
 186  
  *     }
 187  
  * }
 188  
  * }
 189  
  * </pre>
 190  
  *
 191  
  * <p>
 192  
  * To configure the Check passing fields annotated with
 193  
  * </p>
 194  
  * <pre>@com.annotation.CustomAnnotation</pre>:
 195  
 
 196  
  * <p>
 197  
  * &lt;module name=&quot;VisibilityModifier&quot;&gt;
 198  
  *   &lt;property name=&quot;ignoreAnnotationCanonicalNames&quot; value=&quot;
 199  
  *   com.annotation.CustomAnnotation&quot;/&gt;
 200  
  * &lt;/module&gt;
 201  
  * </p>
 202  
  *
 203  
  * <pre>
 204  
  * {@code @com.annotation.CustomAnnotation
 205  
  * String customAnnotated; // No warning
 206  
  * }
 207  
  * {@code @CustomAnnotation
 208  
  * String shortCustomAnnotated; // No warning
 209  
  * }
 210  
  * </pre>
 211  
  *
 212  
  * <p>
 213  
  * To configure the Check passing fields annotated with short annotation name
 214  
  * </p>
 215  
  * <pre>@CustomAnnotation</pre>:
 216  
  *
 217  
  * <p>
 218  
  * &lt;module name=&quot;VisibilityModifier&quot;&gt;
 219  
  *   &lt;property name=&quot;ignoreAnnotationCanonicalNames&quot;
 220  
  *   value=&quot;CustomAnnotation&quot;/&gt;
 221  
  * &lt;/module&gt;
 222  
  * </p>
 223  
  *
 224  
  * <pre>
 225  
  * {@code @CustomAnnotation
 226  
  * String customAnnotated; // No warning
 227  
  * }
 228  
  * {@code @com.annotation.CustomAnnotation
 229  
  * String customAnnotated1; // No warning
 230  
  * }
 231  
  * {@code @mypackage.annotation.CustomAnnotation
 232  
  * String customAnnotatedAnotherPackage; // another package but short name matches
 233  
  *                                       // so no violation
 234  
  * }
 235  
  * </pre>
 236  
  *
 237  
  *
 238  
  * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
 239  
  */
 240  
 @FileStatefulCheck
 241  43
 public class VisibilityModifierCheck
 242  
     extends AbstractCheck {
 243  
 
 244  
     /**
 245  
      * A key is pointing to the warning message text in "messages.properties"
 246  
      * file.
 247  
      */
 248  
     public static final String MSG_KEY = "variable.notPrivate";
 249  
 
 250  
     /** Default immutable types canonical names. */
 251  2
     private static final List<String> DEFAULT_IMMUTABLE_TYPES = Collections.unmodifiableList(
 252  1
         Arrays.stream(new String[] {
 253  
             "java.lang.String",
 254  
             "java.lang.Integer",
 255  
             "java.lang.Byte",
 256  
             "java.lang.Character",
 257  
             "java.lang.Short",
 258  
             "java.lang.Boolean",
 259  
             "java.lang.Long",
 260  
             "java.lang.Double",
 261  
             "java.lang.Float",
 262  
             "java.lang.StackTraceElement",
 263  
             "java.math.BigInteger",
 264  
             "java.math.BigDecimal",
 265  
             "java.io.File",
 266  
             "java.util.Locale",
 267  
             "java.util.UUID",
 268  
             "java.net.URL",
 269  
             "java.net.URI",
 270  
             "java.net.Inet4Address",
 271  
             "java.net.Inet6Address",
 272  
             "java.net.InetSocketAddress",
 273  1
         }).collect(Collectors.toList()));
 274  
 
 275  
     /** Default ignore annotations canonical names. */
 276  2
     private static final List<String> DEFAULT_IGNORE_ANNOTATIONS = Collections.unmodifiableList(
 277  1
         Arrays.stream(new String[] {
 278  
             "org.junit.Rule",
 279  
             "org.junit.ClassRule",
 280  
             "com.google.common.annotations.VisibleForTesting",
 281  1
         }).collect(Collectors.toList()));
 282  
 
 283  
     /** Name for 'public' access modifier. */
 284  
     private static final String PUBLIC_ACCESS_MODIFIER = "public";
 285  
 
 286  
     /** Name for 'private' access modifier. */
 287  
     private static final String PRIVATE_ACCESS_MODIFIER = "private";
 288  
 
 289  
     /** Name for 'protected' access modifier. */
 290  
     private static final String PROTECTED_ACCESS_MODIFIER = "protected";
 291  
 
 292  
     /** Name for implicit 'package' access modifier. */
 293  
     private static final String PACKAGE_ACCESS_MODIFIER = "package";
 294  
 
 295  
     /** Name for 'static' keyword. */
 296  
     private static final String STATIC_KEYWORD = "static";
 297  
 
 298  
     /** Name for 'final' keyword. */
 299  
     private static final String FINAL_KEYWORD = "final";
 300  
 
 301  
     /** Contains explicit access modifiers. */
 302  1
     private static final String[] EXPLICIT_MODS = {
 303  
         PUBLIC_ACCESS_MODIFIER,
 304  
         PRIVATE_ACCESS_MODIFIER,
 305  
         PROTECTED_ACCESS_MODIFIER,
 306  
     };
 307  
 
 308  
     /** Regexp for public members that should be ignored. Note:
 309  
      * Earlier versions of checkstyle used ^f[A-Z][a-zA-Z0-9]*$ as the
 310  
      * default to allow CMP for EJB 1.1 with the default settings.
 311  
      * With EJB 2.0 it is not longer necessary to have public access
 312  
      * for persistent fields.
 313  
      */
 314  43
     private Pattern publicMemberPattern = Pattern.compile("^serialVersionUID$");
 315  
 
 316  
     /** List of ignore annotations short names. */
 317  43
     private final List<String> ignoreAnnotationShortNames =
 318  43
             getClassShortNames(DEFAULT_IGNORE_ANNOTATIONS);
 319  
 
 320  
     /** List of immutable classes short names. */
 321  43
     private final List<String> immutableClassShortNames =
 322  43
         getClassShortNames(DEFAULT_IMMUTABLE_TYPES);
 323  
 
 324  
     /** List of ignore annotations canonical names. */
 325  43
     private List<String> ignoreAnnotationCanonicalNames =
 326  
         new ArrayList<>(DEFAULT_IGNORE_ANNOTATIONS);
 327  
 
 328  
     /** Whether protected members are allowed. */
 329  
     private boolean protectedAllowed;
 330  
 
 331  
     /** Whether package visible members are allowed. */
 332  
     private boolean packageAllowed;
 333  
 
 334  
     /** Allows immutable fields of final classes to be declared as public. */
 335  
     private boolean allowPublicImmutableFields;
 336  
 
 337  
     /** Allows final fields to be declared as public. */
 338  
     private boolean allowPublicFinalFields;
 339  
 
 340  
     /** List of immutable classes canonical names. */
 341  43
     private List<String> immutableClassCanonicalNames = new ArrayList<>(DEFAULT_IMMUTABLE_TYPES);
 342  
 
 343  
     /**
 344  
      * Set the list of ignore annotations.
 345  
      * @param annotationNames array of ignore annotations canonical names.
 346  
      */
 347  
     public void setIgnoreAnnotationCanonicalNames(String... annotationNames) {
 348  4
         ignoreAnnotationCanonicalNames = Arrays.asList(annotationNames);
 349  4
     }
 350  
 
 351  
     /**
 352  
      * Set whether protected members are allowed.
 353  
      * @param protectedAllowed whether protected members are allowed
 354  
      */
 355  
     public void setProtectedAllowed(boolean protectedAllowed) {
 356  1
         this.protectedAllowed = protectedAllowed;
 357  1
     }
 358  
 
 359  
     /**
 360  
      * Set whether package visible members are allowed.
 361  
      * @param packageAllowed whether package visible members are allowed
 362  
      */
 363  
     public void setPackageAllowed(boolean packageAllowed) {
 364  2
         this.packageAllowed = packageAllowed;
 365  2
     }
 366  
 
 367  
     /**
 368  
      * Set the pattern for public members to ignore.
 369  
      * @param pattern
 370  
      *        pattern for public members to ignore.
 371  
      */
 372  
     public void setPublicMemberPattern(Pattern pattern) {
 373  5
         publicMemberPattern = pattern;
 374  5
     }
 375  
 
 376  
     /**
 377  
      * Sets whether public immutable fields are allowed.
 378  
      * @param allow user's value.
 379  
      */
 380  
     public void setAllowPublicImmutableFields(boolean allow) {
 381  13
         allowPublicImmutableFields = allow;
 382  13
     }
 383  
 
 384  
     /**
 385  
      * Sets whether public final fields are allowed.
 386  
      * @param allow user's value.
 387  
      */
 388  
     public void setAllowPublicFinalFields(boolean allow) {
 389  3
         allowPublicFinalFields = allow;
 390  3
     }
 391  
 
 392  
     /**
 393  
      * Set the list of immutable classes types names.
 394  
      * @param classNames array of immutable types canonical names.
 395  
      */
 396  
     public void setImmutableClassCanonicalNames(String... classNames) {
 397  8
         immutableClassCanonicalNames = Arrays.asList(classNames);
 398  8
     }
 399  
 
 400  
     @Override
 401  
     public int[] getDefaultTokens() {
 402  72
         return getRequiredTokens();
 403  
     }
 404  
 
 405  
     @Override
 406  
     public int[] getAcceptableTokens() {
 407  5
         return getRequiredTokens();
 408  
     }
 409  
 
 410  
     @Override
 411  
     public int[] getRequiredTokens() {
 412  150
         return new int[] {
 413  
             TokenTypes.VARIABLE_DEF,
 414  
             TokenTypes.IMPORT,
 415  
         };
 416  
     }
 417  
 
 418  
     @Override
 419  
     public void beginTree(DetailAST rootAst) {
 420  23
         immutableClassShortNames.clear();
 421  23
         final List<String> classShortNames =
 422  23
                 getClassShortNames(immutableClassCanonicalNames);
 423  23
         immutableClassShortNames.addAll(classShortNames);
 424  
 
 425  23
         ignoreAnnotationShortNames.clear();
 426  23
         final List<String> annotationShortNames =
 427  23
                 getClassShortNames(ignoreAnnotationCanonicalNames);
 428  23
         ignoreAnnotationShortNames.addAll(annotationShortNames);
 429  23
     }
 430  
 
 431  
     @Override
 432  
     public void visitToken(DetailAST ast) {
 433  292
         switch (ast.getType()) {
 434  
             case TokenTypes.VARIABLE_DEF:
 435  220
                 if (!isAnonymousClassVariable(ast)) {
 436  208
                     visitVariableDef(ast);
 437  
                 }
 438  
                 break;
 439  
             case TokenTypes.IMPORT:
 440  71
                 visitImport(ast);
 441  71
                 break;
 442  
             default:
 443  1
                 final String exceptionMsg = "Unexpected token type: " + ast.getText();
 444  1
                 throw new IllegalArgumentException(exceptionMsg);
 445  
         }
 446  291
     }
 447  
 
 448  
     /**
 449  
      * Checks if current variable definition is definition of an anonymous class.
 450  
      * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}
 451  
      * @return true if current variable definition is definition of an anonymous class.
 452  
      */
 453  
     private static boolean isAnonymousClassVariable(DetailAST variableDef) {
 454  220
         return variableDef.getParent().getType() != TokenTypes.OBJBLOCK;
 455  
     }
 456  
 
 457  
     /**
 458  
      * Checks access modifier of given variable.
 459  
      * If it is not proper according to Check - puts violation on it.
 460  
      * @param variableDef variable to check.
 461  
      */
 462  
     private void visitVariableDef(DetailAST variableDef) {
 463  208
         final boolean inInterfaceOrAnnotationBlock =
 464  208
                 ScopeUtils.isInInterfaceOrAnnotationBlock(variableDef);
 465  
 
 466  208
         if (!inInterfaceOrAnnotationBlock && !hasIgnoreAnnotation(variableDef)) {
 467  190
             final DetailAST varNameAST = variableDef.findFirstToken(TokenTypes.TYPE)
 468  190
                 .getNextSibling();
 469  190
             final String varName = varNameAST.getText();
 470  190
             if (!hasProperAccessModifier(variableDef, varName)) {
 471  121
                 log(varNameAST.getLineNo(), varNameAST.getColumnNo(),
 472  
                         MSG_KEY, varName);
 473  
             }
 474  
         }
 475  208
     }
 476  
 
 477  
     /**
 478  
      * Checks if variable def has ignore annotation.
 479  
      * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}
 480  
      * @return true if variable def has ignore annotation.
 481  
      */
 482  
     private boolean hasIgnoreAnnotation(DetailAST variableDef) {
 483  203
         final DetailAST firstIgnoreAnnotation =
 484  203
                  findMatchingAnnotation(variableDef);
 485  203
         return firstIgnoreAnnotation != null;
 486  
     }
 487  
 
 488  
     /**
 489  
      * Checks imported type. If type's canonical name was not specified in
 490  
      * <b>immutableClassCanonicalNames</b>, but it's short name collides with one from
 491  
      * <b>immutableClassShortNames</b> - removes it from the last one.
 492  
      * @param importAst {@link TokenTypes#IMPORT Import}
 493  
      */
 494  
     private void visitImport(DetailAST importAst) {
 495  71
         if (!isStarImport(importAst)) {
 496  66
             final DetailAST type = importAst.getFirstChild();
 497  66
             final String canonicalName = getCanonicalName(type);
 498  66
             final String shortName = getClassShortName(canonicalName);
 499  
 
 500  
             // If imported canonical class name is not specified as allowed immutable class,
 501  
             // but its short name collides with one of specified class - removes the short name
 502  
             // from list to avoid names collision
 503  66
             if (!immutableClassCanonicalNames.contains(canonicalName)
 504  53
                      && immutableClassShortNames.contains(shortName)) {
 505  1
                 immutableClassShortNames.remove(shortName);
 506  
             }
 507  66
             if (!ignoreAnnotationCanonicalNames.contains(canonicalName)
 508  63
                      && ignoreAnnotationShortNames.contains(shortName)) {
 509  2
                 ignoreAnnotationShortNames.remove(shortName);
 510  
             }
 511  
         }
 512  71
     }
 513  
 
 514  
     /**
 515  
      * Checks if current import is star import. E.g.:
 516  
      * <p>
 517  
      * {@code
 518  
      * import java.util.*;
 519  
      * }
 520  
      * </p>
 521  
      * @param importAst {@link TokenTypes#IMPORT Import}
 522  
      * @return true if it is star import
 523  
      */
 524  
     private static boolean isStarImport(DetailAST importAst) {
 525  72
         boolean result = false;
 526  72
         DetailAST toVisit = importAst;
 527  730
         while (toVisit != null) {
 528  664
             toVisit = getNextSubTreeNode(toVisit, importAst);
 529  664
             if (toVisit != null && toVisit.getType() == TokenTypes.STAR) {
 530  6
                 result = true;
 531  6
                 break;
 532  
             }
 533  
         }
 534  72
         return result;
 535  
     }
 536  
 
 537  
     /**
 538  
      * Checks if current variable has proper access modifier according to Check's options.
 539  
      * @param variableDef Variable definition node.
 540  
      * @param variableName Variable's name.
 541  
      * @return true if variable has proper access modifier.
 542  
      */
 543  
     private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) {
 544  190
         boolean result = true;
 545  
 
 546  190
         final String variableScope = getVisibilityScope(variableDef);
 547  
 
 548  190
         if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) {
 549  172
             result =
 550  172
                 isStaticFinalVariable(variableDef)
 551  7
                 || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope)
 552  5
                 || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope)
 553  166
                 || isIgnoredPublicMember(variableName, variableScope)
 554  164
                 || isAllowedPublicField(variableDef);
 555  
         }
 556  
 
 557  190
         return result;
 558  
     }
 559  
 
 560  
     /**
 561  
      * Checks whether variable has static final modifiers.
 562  
      * @param variableDef Variable definition node.
 563  
      * @return true of variable has static final modifiers.
 564  
      */
 565  
     private static boolean isStaticFinalVariable(DetailAST variableDef) {
 566  172
         final Set<String> modifiers = getModifiers(variableDef);
 567  344
         return modifiers.contains(STATIC_KEYWORD)
 568  13
                 && modifiers.contains(FINAL_KEYWORD);
 569  
     }
 570  
 
 571  
     /**
 572  
      * Checks whether variable belongs to public members that should be ignored.
 573  
      * @param variableName Variable's name.
 574  
      * @param variableScope Variable's scope.
 575  
      * @return true if variable belongs to public members that should be ignored.
 576  
      */
 577  
     private boolean isIgnoredPublicMember(String variableName, String variableScope) {
 578  332
         return PUBLIC_ACCESS_MODIFIER.equals(variableScope)
 579  137
             && publicMemberPattern.matcher(variableName).find();
 580  
     }
 581  
 
 582  
     /**
 583  
      * Checks whether the variable satisfies the public field check.
 584  
      * @param variableDef Variable definition node.
 585  
      * @return true if allowed.
 586  
      */
 587  
     private boolean isAllowedPublicField(DetailAST variableDef) {
 588  328
         return allowPublicFinalFields && isFinalField(variableDef)
 589  67
             || allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef);
 590  
     }
 591  
 
 592  
     /**
 593  
      * Checks whether immutable field is defined in final class.
 594  
      * @param variableDef Variable definition node.
 595  
      * @return true if immutable field is defined in final class.
 596  
      */
 597  
     private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) {
 598  67
         final DetailAST classDef = variableDef.getParent().getParent();
 599  67
         final Set<String> classModifiers = getModifiers(classDef);
 600  134
         return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF)
 601  66
                 && isImmutableField(variableDef);
 602  
     }
 603  
 
 604  
     /**
 605  
      * Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST.
 606  
      * @param defAST AST for a variable or class definition.
 607  
      * @return the set of modifier Strings for defAST.
 608  
      */
 609  
     private static Set<String> getModifiers(DetailAST defAST) {
 610  429
         final AST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS);
 611  429
         final Set<String> modifiersSet = new HashSet<>();
 612  429
         if (modifiersAST != null) {
 613  428
             AST modifier = modifiersAST.getFirstChild();
 614  1145
             while (modifier != null) {
 615  717
                 modifiersSet.add(modifier.getText());
 616  717
                 modifier = modifier.getNextSibling();
 617  
             }
 618  
         }
 619  429
         return modifiersSet;
 620  
     }
 621  
 
 622  
     /**
 623  
      * Returns the visibility scope for the variable.
 624  
      * @param variableDef Variable definition node.
 625  
      * @return one of "public", "private", "protected", "package"
 626  
      */
 627  
     private static String getVisibilityScope(DetailAST variableDef) {
 628  190
         final Set<String> modifiers = getModifiers(variableDef);
 629  190
         String accessModifier = PACKAGE_ACCESS_MODIFIER;
 630  291
         for (final String modifier : EXPLICIT_MODS) {
 631  274
             if (modifiers.contains(modifier)) {
 632  173
                 accessModifier = modifier;
 633  173
                 break;
 634  
             }
 635  
         }
 636  190
         return accessModifier;
 637  
     }
 638  
 
 639  
     /**
 640  
      * Checks if current field is immutable:
 641  
      * has final modifier and either a primitive type or instance of class
 642  
      * known to be immutable (such as String, ImmutableCollection from Guava and etc).
 643  
      * Classes known to be immutable are listed in
 644  
      * {@link VisibilityModifierCheck#immutableClassCanonicalNames}
 645  
      * @param variableDef Field in consideration.
 646  
      * @return true if field is immutable.
 647  
      */
 648  
     private boolean isImmutableField(DetailAST variableDef) {
 649  66
         boolean result = false;
 650  66
         if (isFinalField(variableDef)) {
 651  53
             final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE);
 652  53
             final boolean isCanonicalName = isCanonicalName(type);
 653  53
             final String typeName = getTypeName(type, isCanonicalName);
 654  53
             final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName);
 655  53
             if (typeArgs == null) {
 656  32
                 result = !isCanonicalName && isPrimitive(type)
 657  25
                     || immutableClassShortNames.contains(typeName)
 658  14
                     || isCanonicalName && immutableClassCanonicalNames.contains(typeName);
 659  
             }
 660  
             else {
 661  21
                 final List<String> argsClassNames = getTypeArgsClassNames(typeArgs);
 662  21
                 result = (immutableClassShortNames.contains(typeName)
 663  4
                     || isCanonicalName && immutableClassCanonicalNames.contains(typeName))
 664  14
                     && areImmutableTypeArguments(argsClassNames);
 665  
             }
 666  
         }
 667  66
         return result;
 668  
     }
 669  
 
 670  
     /**
 671  
      * Checks whether type definition is in canonical form.
 672  
      * @param type type definition token.
 673  
      * @return true if type definition is in canonical form.
 674  
      */
 675  
     private static boolean isCanonicalName(DetailAST type) {
 676  87
         return type.getFirstChild().getType() == TokenTypes.DOT;
 677  
     }
 678  
 
 679  
     /**
 680  
      * Returns generic type arguments token.
 681  
      * @param type type token.
 682  
      * @param isCanonicalName whether type name is in canonical form.
 683  
      * @return generic type arguments token.
 684  
      */
 685  
     private static DetailAST getGenericTypeArgs(DetailAST type, boolean isCanonicalName) {
 686  
         final DetailAST typeArgs;
 687  53
         if (isCanonicalName) {
 688  
             // if type class name is in canonical form, abstract tree has specific structure
 689  18
             typeArgs = type.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS);
 690  
         }
 691  
         else {
 692  35
             typeArgs = type.findFirstToken(TokenTypes.TYPE_ARGUMENTS);
 693  
         }
 694  53
         return typeArgs;
 695  
     }
 696  
 
 697  
     /**
 698  
      * Returns a list of type parameters class names.
 699  
      * @param typeArgs type arguments token.
 700  
      * @return a list of type parameters class names.
 701  
      */
 702  
     private static List<String> getTypeArgsClassNames(DetailAST typeArgs) {
 703  21
         final List<String> typeClassNames = new ArrayList<>();
 704  21
         DetailAST type = typeArgs.findFirstToken(TokenTypes.TYPE_ARGUMENT);
 705  21
         boolean isCanonicalName = isCanonicalName(type);
 706  21
         String typeName = getTypeName(type, isCanonicalName);
 707  21
         typeClassNames.add(typeName);
 708  21
         DetailAST sibling = type.getNextSibling();
 709  34
         while (sibling.getType() == TokenTypes.COMMA) {
 710  13
             type = sibling.getNextSibling();
 711  13
             isCanonicalName = isCanonicalName(type);
 712  13
             typeName = getTypeName(type, isCanonicalName);
 713  13
             typeClassNames.add(typeName);
 714  13
             sibling = type.getNextSibling();
 715  
         }
 716  21
         return typeClassNames;
 717  
     }
 718  
 
 719  
     /**
 720  
      * Checks whether all of generic type arguments are immutable.
 721  
      * If at least one argument is mutable, we assume that the whole list of type arguments
 722  
      * is mutable.
 723  
      * @param typeArgsClassNames type arguments class names.
 724  
      * @return true if all of generic type arguments are immutable.
 725  
      */
 726  
     private boolean areImmutableTypeArguments(List<String> typeArgsClassNames) {
 727  14
         return typeArgsClassNames.stream().noneMatch(
 728  
             typeName -> {
 729  44
                 return !immutableClassShortNames.contains(typeName)
 730  11
                     && !immutableClassCanonicalNames.contains(typeName);
 731  
             });
 732  
     }
 733  
 
 734  
     /**
 735  
      * Checks whether current field is final.
 736  
      * @param variableDef field in consideration.
 737  
      * @return true if current field is final.
 738  
      */
 739  
     private static boolean isFinalField(DetailAST variableDef) {
 740  85
         final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS);
 741  85
         return modifiers.findFirstToken(TokenTypes.FINAL) != null;
 742  
     }
 743  
 
 744  
     /**
 745  
      * Gets the name of type from given ast {@link TokenTypes#TYPE TYPE} node.
 746  
      * If type is specified via its canonical name - canonical name will be returned,
 747  
      * else - short type's name.
 748  
      * @param type {@link TokenTypes#TYPE TYPE} node.
 749  
      * @param isCanonicalName is given name canonical.
 750  
      * @return String representation of given type's name.
 751  
      */
 752  
     private static String getTypeName(DetailAST type, boolean isCanonicalName) {
 753  
         final String typeName;
 754  87
         if (isCanonicalName) {
 755  26
             typeName = getCanonicalName(type);
 756  
         }
 757  
         else {
 758  61
             typeName = type.getFirstChild().getText();
 759  
         }
 760  87
         return typeName;
 761  
     }
 762  
 
 763  
     /**
 764  
      * Checks if current type is primitive type (int, short, float, boolean, double, etc.).
 765  
      * As primitive types have special tokens for each one, such as:
 766  
      * LITERAL_INT, LITERAL_BOOLEAN, etc.
 767  
      * So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a
 768  
      * primitive type.
 769  
      * @param type Ast {@link TokenTypes#TYPE TYPE} node.
 770  
      * @return true if current type is primitive type.
 771  
      */
 772  
     private static boolean isPrimitive(DetailAST type) {
 773  18
         return type.getFirstChild().getType() != TokenTypes.IDENT;
 774  
     }
 775  
 
 776  
     /**
 777  
      * Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node.
 778  
      * @param type DetailAST {@link TokenTypes#TYPE TYPE} node.
 779  
      * @return canonical type's name
 780  
      */
 781  
     private static String getCanonicalName(DetailAST type) {
 782  92
         final StringBuilder canonicalNameBuilder = new StringBuilder(256);
 783  92
         DetailAST toVisit = type.getFirstChild();
 784  656
         while (toVisit != null) {
 785  570
             toVisit = getNextSubTreeNode(toVisit, type);
 786  570
             if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) {
 787  367
                 canonicalNameBuilder.append(toVisit.getText());
 788  367
                 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type);
 789  367
                 if (nextSubTreeNode != null) {
 790  281
                     if (nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) {
 791  6
                         break;
 792  
                     }
 793  275
                     canonicalNameBuilder.append('.');
 794  
                 }
 795  361
             }
 796  
         }
 797  92
         return canonicalNameBuilder.toString();
 798  
     }
 799  
 
 800  
     /**
 801  
      * Gets the next node of a syntactical tree (child of a current node or
 802  
      * sibling of a current node, or sibling of a parent of a current node).
 803  
      * @param currentNodeAst Current node in considering
 804  
      * @param subTreeRootAst SubTree root
 805  
      * @return Current node after bypassing, if current node reached the root of a subtree
 806  
      *        method returns null
 807  
      */
 808  
     private static DetailAST
 809  
         getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) {
 810  1601
         DetailAST currentNode = currentNodeAst;
 811  1601
         DetailAST toVisitAst = currentNode.getFirstChild();
 812  3083
         while (toVisitAst == null) {
 813  1720
             toVisitAst = currentNode.getNextSibling();
 814  1720
             if (toVisitAst == null) {
 815  868
                 if (currentNode.getParent().equals(subTreeRootAst)
 816  524
                          && currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) {
 817  238
                     break;
 818  
                 }
 819  630
                 currentNode = currentNode.getParent();
 820  
             }
 821  
         }
 822  1601
         return toVisitAst;
 823  
     }
 824  
 
 825  
     /**
 826  
      * Gets the list with short names classes.
 827  
      * These names are taken from array of classes canonical names.
 828  
      * @param canonicalClassNames canonical class names.
 829  
      * @return the list of short names of classes.
 830  
      */
 831  
     private static List<String> getClassShortNames(List<String> canonicalClassNames) {
 832  132
         final List<String> shortNames = new ArrayList<>();
 833  132
         for (String canonicalClassName : canonicalClassNames) {
 834  1406
             final String shortClassName = canonicalClassName
 835  2812
                     .substring(canonicalClassName.lastIndexOf('.') + 1,
 836  1406
                     canonicalClassName.length());
 837  1406
             shortNames.add(shortClassName);
 838  1406
         }
 839  132
         return shortNames;
 840  
     }
 841  
 
 842  
     /**
 843  
      * Gets the short class name from given canonical name.
 844  
      * @param canonicalClassName canonical class name.
 845  
      * @return short name of class.
 846  
      */
 847  
     private static String getClassShortName(String canonicalClassName) {
 848  132
         return canonicalClassName
 849  132
                 .substring(canonicalClassName.lastIndexOf('.') + 1,
 850  66
                 canonicalClassName.length());
 851  
     }
 852  
 
 853  
     /**
 854  
      * Checks whether the AST is annotated with
 855  
      * an annotation containing the passed in regular
 856  
      * expression and return the AST representing that
 857  
      * annotation.
 858  
      *
 859  
      * <p>
 860  
      * This method will not look for imports or package
 861  
      * statements to detect the passed in annotation.
 862  
      * </p>
 863  
      *
 864  
      * <p>
 865  
      * To check if an AST contains a passed in annotation
 866  
      * taking into account fully-qualified names
 867  
      * (ex: java.lang.Override, Override)
 868  
      * this method will need to be called twice. Once for each
 869  
      * name given.
 870  
      * </p>
 871  
      *
 872  
      * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}.
 873  
      * @return the AST representing the first such annotation or null if
 874  
      *         no such annotation was found
 875  
      */
 876  
     private DetailAST findMatchingAnnotation(DetailAST variableDef) {
 877  203
         DetailAST matchingAnnotation = null;
 878  
 
 879  203
         final DetailAST holder = AnnotationUtility.getAnnotationHolder(variableDef);
 880  
 
 881  203
         for (DetailAST child = holder.getFirstChild();
 882  833
             child != null; child = child.getNextSibling()) {
 883  328
             if (child.getType() == TokenTypes.ANNOTATION) {
 884  41
                 final DetailAST ast = child.getFirstChild();
 885  41
                 final String name =
 886  41
                     FullIdent.createFullIdent(ast.getNextSibling()).getText();
 887  41
                 if (ignoreAnnotationCanonicalNames.contains(name)
 888  36
                          || ignoreAnnotationShortNames.contains(name)) {
 889  13
                     matchingAnnotation = child;
 890  13
                     break;
 891  
                 }
 892  
             }
 893  
         }
 894  
 
 895  203
         return matchingAnnotation;
 896  
     }
 897  
 }