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