001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 * <div> 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 * </div> 046 * 047 * <p> 048 * Public members are not flagged if the name matches the public 049 * member regular expression (contains {@code "^serialVersionUID$"} by 050 * default). 051 * </p> 052 * 053 * <p> 054 * Note that Checkstyle 2 used to include {@code "^f[A-Z][a-zA-Z0-9]*$"} in the default pattern 055 * to allow names used in container-managed persistence for Enterprise JavaBeans (EJB) 1.1 with 056 * the default settings. With EJB 2.0 it is no longer necessary to have public access for 057 * persistent fields, so the default has been changed. 058 * </p> 059 * 060 * <p> 061 * Rationale: Enforce encapsulation. 062 * </p> 063 * 064 * <p> 065 * Check also has options making it less strict: 066 * </p> 067 * 068 * <p> 069 * <b>ignoreAnnotationCanonicalNames</b> - the list of annotations which ignore 070 * variables in consideration. If user want to provide short annotation name that 071 * type will match to any named the same type without consideration of package. 072 * </p> 073 * 074 * <p> 075 * <b>allowPublicFinalFields</b> - which allows public final fields. 076 * </p> 077 * 078 * <p> 079 * <b>allowPublicImmutableFields</b> - which allows immutable fields to be 080 * declared as public if defined in final class. 081 * </p> 082 * 083 * <p> 084 * Field is known to be immutable if: 085 * </p> 086 * <ul> 087 * <li>It's declared as final</li> 088 * <li>Has either a primitive type or instance of class user defined to be immutable 089 * (such as String, ImmutableCollection from Guava, etc.)</li> 090 * </ul> 091 * 092 * <p> 093 * Classes known to be immutable are listed in <b>immutableClassCanonicalNames</b> 094 * by their canonical names. 095 * </p> 096 * 097 * <p> 098 * Property Rationale: Forcing all fields of class to have private modifier by default is 099 * good in most cases, but in some cases it drawbacks in too much boilerplate get/set code. 100 * One of such cases are immutable classes. 101 * </p> 102 * 103 * <p> 104 * Restriction: Check doesn't check if class is immutable, there's no checking 105 * if accessory methods are missing and all fields are immutable, we only check 106 * if current field is immutable or final. 107 * Under the flag <b>allowPublicImmutableFields</b>, the enclosing class must 108 * also be final, to encourage immutability. 109 * Under the flag <b>allowPublicFinalFields</b>, the final modifier 110 * on the enclosing class is optional. 111 * </p> 112 * 113 * <p> 114 * Star imports are out of scope of this Check. So if one of type imported via 115 * star import collides with user specified one by its short name - there 116 * won't be Check's violation. 117 * </p> 118 * 119 * @since 3.0 120 */ 121@FileStatefulCheck 122public class VisibilityModifierCheck 123 extends AbstractCheck { 124 125 /** 126 * A key is pointing to the warning message text in "messages.properties" 127 * file. 128 */ 129 public static final String MSG_KEY = "variable.notPrivate"; 130 131 /** Default immutable types canonical names. */ 132 private static final Set<String> DEFAULT_IMMUTABLE_TYPES = Set.of( 133 "java.lang.String", 134 "java.lang.Integer", 135 "java.lang.Byte", 136 "java.lang.Character", 137 "java.lang.Short", 138 "java.lang.Boolean", 139 "java.lang.Long", 140 "java.lang.Double", 141 "java.lang.Float", 142 "java.lang.StackTraceElement", 143 "java.math.BigInteger", 144 "java.math.BigDecimal", 145 "java.io.File", 146 "java.util.Locale", 147 "java.util.UUID", 148 "java.net.URL", 149 "java.net.URI", 150 "java.net.Inet4Address", 151 "java.net.Inet6Address", 152 "java.net.InetSocketAddress" 153 ); 154 155 /** Default ignore annotations canonical names. */ 156 private static final Set<String> DEFAULT_IGNORE_ANNOTATIONS = Set.of( 157 "org.junit.Rule", 158 "org.junit.ClassRule", 159 "com.google.common.annotations.VisibleForTesting" 160 ); 161 162 /** Name for 'public' access modifier. */ 163 private static final String PUBLIC_ACCESS_MODIFIER = "public"; 164 165 /** Name for 'private' access modifier. */ 166 private static final String PRIVATE_ACCESS_MODIFIER = "private"; 167 168 /** Name for 'protected' access modifier. */ 169 private static final String PROTECTED_ACCESS_MODIFIER = "protected"; 170 171 /** Name for implicit 'package' access modifier. */ 172 private static final String PACKAGE_ACCESS_MODIFIER = "package"; 173 174 /** Name for 'static' keyword. */ 175 private static final String STATIC_KEYWORD = "static"; 176 177 /** Name for 'final' keyword. */ 178 private static final String FINAL_KEYWORD = "final"; 179 180 /** Contains explicit access modifiers. */ 181 private static final String[] EXPLICIT_MODS = { 182 PUBLIC_ACCESS_MODIFIER, 183 PRIVATE_ACCESS_MODIFIER, 184 PROTECTED_ACCESS_MODIFIER, 185 }; 186 187 /** 188 * Specify pattern for public members that should be ignored. 189 */ 190 private Pattern publicMemberPattern = Pattern.compile("^serialVersionUID$"); 191 192 /** Set of ignore annotations short names. */ 193 private Set<String> ignoreAnnotationShortNames; 194 195 /** Set of immutable classes short names. */ 196 private Set<String> immutableClassShortNames; 197 198 /** 199 * Specify annotations canonical names which ignore variables in 200 * consideration. 201 */ 202 private Set<String> ignoreAnnotationCanonicalNames = DEFAULT_IGNORE_ANNOTATIONS; 203 204 /** Control whether protected members are allowed. */ 205 private boolean protectedAllowed; 206 207 /** Control whether package visible members are allowed. */ 208 private boolean packageAllowed; 209 210 /** Allow immutable fields to be declared as public if defined in final class. */ 211 private boolean allowPublicImmutableFields; 212 213 /** Allow final fields to be declared as public. */ 214 private boolean allowPublicFinalFields; 215 216 /** Specify immutable classes canonical names. */ 217 private Set<String> immutableClassCanonicalNames = DEFAULT_IMMUTABLE_TYPES; 218 219 /** 220 * Setter to specify annotations canonical names which ignore variables 221 * in consideration. 222 * 223 * @param annotationNames array of ignore annotations canonical names. 224 * @since 6.5 225 */ 226 public void setIgnoreAnnotationCanonicalNames(String... annotationNames) { 227 ignoreAnnotationCanonicalNames = Set.of(annotationNames); 228 } 229 230 /** 231 * Setter to control whether protected members are allowed. 232 * 233 * @param protectedAllowed whether protected members are allowed 234 * @since 3.0 235 */ 236 public void setProtectedAllowed(boolean protectedAllowed) { 237 this.protectedAllowed = protectedAllowed; 238 } 239 240 /** 241 * Setter to control whether package visible members are allowed. 242 * 243 * @param packageAllowed whether package visible members are allowed 244 * @since 3.0 245 */ 246 public void setPackageAllowed(boolean packageAllowed) { 247 this.packageAllowed = packageAllowed; 248 } 249 250 /** 251 * Setter to specify pattern for public members that should be ignored. 252 * 253 * @param pattern 254 * pattern for public members to ignore. 255 * @since 3.0 256 */ 257 public void setPublicMemberPattern(Pattern pattern) { 258 publicMemberPattern = pattern; 259 } 260 261 /** 262 * Setter to allow immutable fields to be declared as public if defined in final class. 263 * 264 * @param allow user's value. 265 * @since 6.4 266 */ 267 public void setAllowPublicImmutableFields(boolean allow) { 268 allowPublicImmutableFields = allow; 269 } 270 271 /** 272 * Setter to allow final fields to be declared as public. 273 * 274 * @param allow user's value. 275 * @since 7.0 276 */ 277 public void setAllowPublicFinalFields(boolean allow) { 278 allowPublicFinalFields = allow; 279 } 280 281 /** 282 * Setter to specify immutable classes canonical names. 283 * 284 * @param classNames array of immutable types canonical names. 285 * @since 6.4.1 286 */ 287 public void setImmutableClassCanonicalNames(String... classNames) { 288 immutableClassCanonicalNames = Set.of(classNames); 289 } 290 291 @Override 292 public int[] getDefaultTokens() { 293 return getRequiredTokens(); 294 } 295 296 @Override 297 public int[] getAcceptableTokens() { 298 return getRequiredTokens(); 299 } 300 301 @Override 302 public int[] getRequiredTokens() { 303 return new int[] { 304 TokenTypes.VARIABLE_DEF, 305 TokenTypes.IMPORT, 306 }; 307 } 308 309 @Override 310 public void beginTree(DetailAST rootAst) { 311 immutableClassShortNames = getClassShortNames(immutableClassCanonicalNames); 312 ignoreAnnotationShortNames = getClassShortNames(ignoreAnnotationCanonicalNames); 313 } 314 315 @Override 316 public void visitToken(DetailAST ast) { 317 switch (ast.getType()) { 318 case TokenTypes.VARIABLE_DEF -> { 319 if (!isAnonymousClassVariable(ast)) { 320 visitVariableDef(ast); 321 } 322 } 323 case TokenTypes.IMPORT -> visitImport(ast); 324 default -> { 325 final String exceptionMsg = "Unexpected token type: " + ast.getText(); 326 throw new IllegalArgumentException(exceptionMsg); 327 } 328 } 329 } 330 331 /** 332 * Checks if current variable definition is definition of an anonymous class. 333 * 334 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 335 * @return true if current variable definition is definition of an anonymous class. 336 */ 337 private static boolean isAnonymousClassVariable(DetailAST variableDef) { 338 return variableDef.getParent().getType() != TokenTypes.OBJBLOCK; 339 } 340 341 /** 342 * Checks access modifier of given variable. 343 * If it is not proper according to Check - puts violation on it. 344 * 345 * @param variableDef variable to check. 346 */ 347 private void visitVariableDef(DetailAST variableDef) { 348 final boolean inInterfaceOrAnnotationBlock = 349 ScopeUtil.isInInterfaceOrAnnotationBlock(variableDef); 350 351 if (!inInterfaceOrAnnotationBlock && !hasIgnoreAnnotation(variableDef)) { 352 final DetailAST varNameAST = variableDef.findFirstToken(TokenTypes.TYPE) 353 .getNextSibling(); 354 final String varName = varNameAST.getText(); 355 if (!hasProperAccessModifier(variableDef, varName)) { 356 log(varNameAST, MSG_KEY, varName); 357 } 358 } 359 } 360 361 /** 362 * Checks if variable def has ignore annotation. 363 * 364 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 365 * @return true if variable def has ignore annotation. 366 */ 367 private boolean hasIgnoreAnnotation(DetailAST variableDef) { 368 final DetailAST firstIgnoreAnnotation = 369 findMatchingAnnotation(variableDef); 370 return firstIgnoreAnnotation != null; 371 } 372 373 /** 374 * Checks imported type. If type's canonical name was not specified in 375 * <b>immutableClassCanonicalNames</b>, but its short name collides with one from 376 * <b>immutableClassShortNames</b> - removes it from the last one. 377 * 378 * @param importAst {@link TokenTypes#IMPORT Import} 379 */ 380 private void visitImport(DetailAST importAst) { 381 if (!isStarImport(importAst)) { 382 final String canonicalName = getCanonicalName(importAst); 383 final String shortName = getClassShortName(canonicalName); 384 385 // If imported canonical class name is not specified as allowed immutable class, 386 // but its short name collides with one of specified class - removes the short name 387 // from list to avoid names collision 388 if (!immutableClassCanonicalNames.contains(canonicalName)) { 389 immutableClassShortNames.remove(shortName); 390 } 391 if (!ignoreAnnotationCanonicalNames.contains(canonicalName)) { 392 ignoreAnnotationShortNames.remove(shortName); 393 } 394 } 395 } 396 397 /** 398 * Checks if current import is star import. E.g.: 399 * 400 * <p> 401 * {@code 402 * import java.util.*; 403 * } 404 * </p> 405 * 406 * @param importAst {@link TokenTypes#IMPORT Import} 407 * @return true if it is star import 408 */ 409 private static boolean isStarImport(DetailAST importAst) { 410 boolean result = false; 411 DetailAST toVisit = importAst; 412 while (toVisit != null) { 413 toVisit = getNextSubTreeNode(toVisit, importAst); 414 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) { 415 result = true; 416 break; 417 } 418 } 419 return result; 420 } 421 422 /** 423 * Checks if current variable has proper access modifier according to Check's options. 424 * 425 * @param variableDef Variable definition node. 426 * @param variableName Variable's name. 427 * @return true if variable has proper access modifier. 428 */ 429 private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) { 430 boolean result = true; 431 432 final String variableScope = getVisibilityScope(variableDef); 433 434 if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) { 435 result = 436 isStaticFinalVariable(variableDef) 437 || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope) 438 || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope) 439 || isIgnoredPublicMember(variableName, variableScope) 440 || isAllowedPublicField(variableDef); 441 } 442 443 return result; 444 } 445 446 /** 447 * Checks whether variable has static final modifiers. 448 * 449 * @param variableDef Variable definition node. 450 * @return true of variable has static final modifiers. 451 */ 452 private static boolean isStaticFinalVariable(DetailAST variableDef) { 453 final Set<String> modifiers = getModifiers(variableDef); 454 return modifiers.contains(STATIC_KEYWORD) 455 && modifiers.contains(FINAL_KEYWORD); 456 } 457 458 /** 459 * Checks whether variable belongs to public members that should be ignored. 460 * 461 * @param variableName Variable's name. 462 * @param variableScope Variable's scope. 463 * @return true if variable belongs to public members that should be ignored. 464 */ 465 private boolean isIgnoredPublicMember(String variableName, String variableScope) { 466 return PUBLIC_ACCESS_MODIFIER.equals(variableScope) 467 && publicMemberPattern.matcher(variableName).find(); 468 } 469 470 /** 471 * Checks whether the variable satisfies the public field check. 472 * 473 * @param variableDef Variable definition node. 474 * @return true if allowed. 475 */ 476 private boolean isAllowedPublicField(DetailAST variableDef) { 477 return allowPublicFinalFields && isFinalField(variableDef) 478 || allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef); 479 } 480 481 /** 482 * Checks whether immutable field is defined in final class. 483 * 484 * @param variableDef Variable definition node. 485 * @return true if immutable field is defined in final class. 486 */ 487 private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) { 488 final DetailAST classDef = variableDef.getParent().getParent(); 489 final Set<String> classModifiers = getModifiers(classDef); 490 return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF) 491 && isImmutableField(variableDef); 492 } 493 494 /** 495 * Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST. 496 * 497 * @param defAST AST for a variable or class definition. 498 * @return the set of modifier Strings for defAST. 499 */ 500 private static Set<String> getModifiers(DetailAST defAST) { 501 final DetailAST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS); 502 final Set<String> modifiersSet = new HashSet<>(); 503 if (modifiersAST != null) { 504 DetailAST modifier = modifiersAST.getFirstChild(); 505 while (modifier != null) { 506 modifiersSet.add(modifier.getText()); 507 modifier = modifier.getNextSibling(); 508 } 509 } 510 return modifiersSet; 511 } 512 513 /** 514 * Returns the visibility scope for the variable. 515 * 516 * @param variableDef Variable definition node. 517 * @return one of "public", "private", "protected", "package" 518 */ 519 private static String getVisibilityScope(DetailAST variableDef) { 520 final Set<String> modifiers = getModifiers(variableDef); 521 String accessModifier = PACKAGE_ACCESS_MODIFIER; 522 for (final String modifier : EXPLICIT_MODS) { 523 if (modifiers.contains(modifier)) { 524 accessModifier = modifier; 525 break; 526 } 527 } 528 return accessModifier; 529 } 530 531 /** 532 * Checks if current field is immutable: 533 * has final modifier and either a primitive type or instance of class 534 * known to be immutable (such as String, ImmutableCollection from Guava, etc.). 535 * Classes known to be immutable are listed in 536 * {@link VisibilityModifierCheck#immutableClassCanonicalNames} 537 * 538 * @param variableDef Field in consideration. 539 * @return true if field is immutable. 540 */ 541 private boolean isImmutableField(DetailAST variableDef) { 542 boolean result = false; 543 if (isFinalField(variableDef)) { 544 final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE); 545 final boolean isCanonicalName = isCanonicalName(type); 546 final String typeName = getCanonicalName(type); 547 if (immutableClassShortNames.contains(typeName) 548 || isCanonicalName && immutableClassCanonicalNames.contains(typeName)) { 549 final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName); 550 551 if (typeArgs == null) { 552 result = true; 553 } 554 else { 555 final List<String> argsClassNames = getTypeArgsClassNames(typeArgs); 556 result = areImmutableTypeArguments(argsClassNames); 557 } 558 } 559 else { 560 result = !isCanonicalName && isPrimitive(type); 561 } 562 } 563 return result; 564 } 565 566 /** 567 * Checks whether type definition is in canonical form. 568 * 569 * @param type type definition token. 570 * @return true if type definition is in canonical form. 571 */ 572 private static boolean isCanonicalName(DetailAST type) { 573 return type.getFirstChild().getType() == TokenTypes.DOT; 574 } 575 576 /** 577 * Returns generic type arguments token. 578 * 579 * @param type type token. 580 * @param isCanonicalName whether type name is in canonical form. 581 * @return generic type arguments token. 582 */ 583 private static DetailAST getGenericTypeArgs(DetailAST type, boolean isCanonicalName) { 584 final DetailAST typeArgs; 585 if (isCanonicalName) { 586 // if type class name is in canonical form, abstract tree has specific structure 587 typeArgs = type.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS); 588 } 589 else { 590 typeArgs = type.findFirstToken(TokenTypes.TYPE_ARGUMENTS); 591 } 592 return typeArgs; 593 } 594 595 /** 596 * Returns a list of type parameters class names. 597 * 598 * @param typeArgs type arguments token. 599 * @return a list of type parameters class names. 600 */ 601 private static List<String> getTypeArgsClassNames(DetailAST typeArgs) { 602 final List<String> typeClassNames = new ArrayList<>(); 603 DetailAST type = typeArgs.findFirstToken(TokenTypes.TYPE_ARGUMENT); 604 DetailAST sibling; 605 do { 606 final String typeName = getCanonicalName(type); 607 typeClassNames.add(typeName); 608 sibling = type.getNextSibling(); 609 type = sibling.getNextSibling(); 610 } while (sibling.getType() == TokenTypes.COMMA); 611 return typeClassNames; 612 } 613 614 /** 615 * Checks whether all generic type arguments are immutable. 616 * If at least one argument is mutable, we assume that the whole list of type arguments 617 * is mutable. 618 * 619 * @param typeArgsClassNames type arguments class names. 620 * @return true if all generic type arguments are immutable. 621 */ 622 private boolean areImmutableTypeArguments(Collection<String> typeArgsClassNames) { 623 return typeArgsClassNames.stream().noneMatch( 624 typeName -> { 625 return !immutableClassShortNames.contains(typeName) 626 && !immutableClassCanonicalNames.contains(typeName); 627 }); 628 } 629 630 /** 631 * Checks whether current field is final. 632 * 633 * @param variableDef field in consideration. 634 * @return true if current field is final. 635 */ 636 private static boolean isFinalField(DetailAST variableDef) { 637 final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS); 638 return modifiers.findFirstToken(TokenTypes.FINAL) != null; 639 } 640 641 /** 642 * Checks if current type is primitive type (int, short, float, boolean, double, etc.). 643 * As primitive types have special tokens for each one, such as: 644 * LITERAL_INT, LITERAL_BOOLEAN, etc. 645 * So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a 646 * primitive type. 647 * 648 * @param type Ast {@link TokenTypes#TYPE TYPE} node. 649 * @return true if current type is primitive type. 650 */ 651 private static boolean isPrimitive(DetailAST type) { 652 return type.getFirstChild().getType() != TokenTypes.IDENT; 653 } 654 655 /** 656 * Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node. 657 * 658 * @param type DetailAST {@link TokenTypes#TYPE TYPE} node. 659 * @return canonical type's name 660 */ 661 private static String getCanonicalName(DetailAST type) { 662 final StringBuilder canonicalNameBuilder = new StringBuilder(256); 663 DetailAST toVisit = type; 664 while (toVisit != null) { 665 toVisit = getNextSubTreeNode(toVisit, type); 666 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) { 667 if (!canonicalNameBuilder.isEmpty()) { 668 canonicalNameBuilder.append('.'); 669 } 670 canonicalNameBuilder.append(toVisit.getText()); 671 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type); 672 if (nextSubTreeNode != null 673 && nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) { 674 break; 675 } 676 } 677 } 678 return canonicalNameBuilder.toString(); 679 } 680 681 /** 682 * Gets the next node of a syntactical tree (child of a current node or 683 * sibling of a current node, or sibling of a parent of a current node). 684 * 685 * @param currentNodeAst Current node in considering 686 * @param subTreeRootAst SubTree root 687 * @return Current node after bypassing, if current node reached the root of a subtree 688 * method returns null 689 */ 690 private static DetailAST 691 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) { 692 DetailAST currentNode = currentNodeAst; 693 DetailAST toVisitAst = currentNode.getFirstChild(); 694 while (toVisitAst == null) { 695 toVisitAst = currentNode.getNextSibling(); 696 if (currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) { 697 break; 698 } 699 currentNode = currentNode.getParent(); 700 } 701 return toVisitAst; 702 } 703 704 /** 705 * Converts canonical class names to short names. 706 * 707 * @param canonicalClassNames the set of canonical class names. 708 * @return the set of short names of classes. 709 */ 710 private static Set<String> getClassShortNames(Set<String> canonicalClassNames) { 711 return canonicalClassNames.stream() 712 .map(CommonUtil::baseClassName) 713 .collect(Collectors.toCollection(HashSet::new)); 714 } 715 716 /** 717 * Gets the short class name from given canonical name. 718 * 719 * @param canonicalClassName canonical class name. 720 * @return short name of class. 721 */ 722 private static String getClassShortName(String canonicalClassName) { 723 return canonicalClassName 724 .substring(canonicalClassName.lastIndexOf('.') + 1); 725 } 726 727 /** 728 * Checks whether the AST is annotated with 729 * an annotation containing the passed in regular 730 * expression and return the AST representing that 731 * annotation. 732 * 733 * <p> 734 * This method will not look for imports or package 735 * statements to detect the passed in annotation. 736 * </p> 737 * 738 * <p> 739 * To check if an AST contains a passed in annotation 740 * taking into account fully-qualified names 741 * (ex: java.lang.Override, Override) 742 * this method will need to be called twice. Once for each 743 * name given. 744 * </p> 745 * 746 * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}. 747 * @return the AST representing the first such annotation or null if 748 * no such annotation was found 749 */ 750 private DetailAST findMatchingAnnotation(DetailAST variableDef) { 751 DetailAST matchingAnnotation = null; 752 753 final DetailAST holder = AnnotationUtil.getAnnotationHolder(variableDef); 754 755 for (DetailAST child = holder.getFirstChild(); 756 child != null; child = child.getNextSibling()) { 757 if (child.getType() == TokenTypes.ANNOTATION) { 758 final DetailAST ast = child.getFirstChild(); 759 final String name = 760 FullIdent.createFullIdent(ast.getNextSibling()).getText(); 761 if (ignoreAnnotationCanonicalNames.contains(name) 762 || ignoreAnnotationShortNames.contains(name)) { 763 matchingAnnotation = child; 764 break; 765 } 766 } 767 } 768 769 return matchingAnnotation; 770 } 771 772}