001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.design; 021 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Set; 028import java.util.regex.Pattern; 029import java.util.stream.Collectors; 030 031import antlr.collections.AST; 032import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 033import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 034import com.puppycrawl.tools.checkstyle.api.DetailAST; 035import com.puppycrawl.tools.checkstyle.api.FullIdent; 036import com.puppycrawl.tools.checkstyle.api.TokenTypes; 037import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 038import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 039 040/** 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 allowProtected/Package is set. 044 * <p> 045 * Public members are not flagged if the name matches the public 046 * member regular expression (contains "^serialVersionUID$" by 047 * default). 048 * </p> 049 * Rationale: Enforce encapsulation. 050 * <p> 051 * Check also has options making it less strict: 052 * </p> 053 * <p> 054 * <b>ignoreAnnotationCanonicalNames</b> - the list of annotations canonical names 055 * which ignore variables in consideration, if user will provide short annotation name 056 * that type will match to any named the same type without consideration of package, 057 * list by default: 058 * </p> 059 * <ul> 060 * <li>org.junit.Rule</li> 061 * <li>org.junit.ClassRule</li> 062 * <li>com.google.common.annotations.VisibleForTesting</li> 063 * </ul> 064 * <p> 065 * For example such public field will be skipped by default value of list above: 066 * </p> 067 * 068 * <pre> 069 * {@code @org.junit.Rule 070 * public TemporaryFolder publicJUnitRule = new TemporaryFolder(); 071 * } 072 * </pre> 073 * 074 * <p> 075 * <b>allowPublicFinalFields</b> - which allows public final fields. Default value is <b>false</b>. 076 * </p> 077 * <p> 078 * <b>allowPublicImmutableFields</b> - which allows immutable fields to be 079 * declared as public if defined in final class. Default value is <b>false</b> 080 * </p> 081 * <p> 082 * Field is known to be immutable if: 083 * </p> 084 * <ul> 085 * <li>It's declared as final</li> 086 * <li>Has either a primitive type or instance of class user defined to be immutable 087 * (such as String, ImmutableCollection from Guava and etc)</li> 088 * </ul> 089 * <p> 090 * Classes known to be immutable are listed in <b>immutableClassCanonicalNames</b> by their 091 * <b>canonical</b> names. List by default: 092 * </p> 093 * <ul> 094 * <li>java.lang.String</li> 095 * <li>java.lang.Integer</li> 096 * <li>java.lang.Byte</li> 097 * <li>java.lang.Character</li> 098 * <li>java.lang.Short</li> 099 * <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 * <module name="VisibilityModifier"> 165 * <property name="allowPublicImmutableFields" value="true"/> 166 * <property name="immutableClassCanonicalNames" value="java.util.List, 167 * com.google.common.collect.ImmutableSet"/> 168 * </module> 169 * </p> 170 * 171 * <pre> 172 * {@code 173 * public final class ImmutableClass 174 * { 175 * public final ImmutableSet<String> includes; // No warning 176 * public final ImmutableSet<String> excludes; // No warning 177 * public final BigDecimal value; // Warning here, type BigDecimal isn't specified as immutable 178 * 179 * public ImmutableClass(Collection<String> includes, Collection<String> 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 * <module name="VisibilityModifier"> 198 * <property name="ignoreAnnotationCanonicalNames" value=" 199 * com.annotation.CustomAnnotation"/> 200 * </module> 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 * <module name="VisibilityModifier"> 219 * <property name="ignoreAnnotationCanonicalNames" 220 * value="CustomAnnotation"/> 221 * </module> 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 */ 239@FileStatefulCheck 240public class VisibilityModifierCheck 241 extends AbstractCheck { 242 243 /** 244 * A key is pointing to the warning message text in "messages.properties" 245 * file. 246 */ 247 public static final String MSG_KEY = "variable.notPrivate"; 248 249 /** Default immutable types canonical names. */ 250 private static final List<String> DEFAULT_IMMUTABLE_TYPES = Collections.unmodifiableList( 251 Arrays.stream(new String[] { 252 "java.lang.String", 253 "java.lang.Integer", 254 "java.lang.Byte", 255 "java.lang.Character", 256 "java.lang.Short", 257 "java.lang.Boolean", 258 "java.lang.Long", 259 "java.lang.Double", 260 "java.lang.Float", 261 "java.lang.StackTraceElement", 262 "java.math.BigInteger", 263 "java.math.BigDecimal", 264 "java.io.File", 265 "java.util.Locale", 266 "java.util.UUID", 267 "java.net.URL", 268 "java.net.URI", 269 "java.net.Inet4Address", 270 "java.net.Inet6Address", 271 "java.net.InetSocketAddress", 272 }).collect(Collectors.toList())); 273 274 /** Default ignore annotations canonical names. */ 275 private static final List<String> DEFAULT_IGNORE_ANNOTATIONS = Collections.unmodifiableList( 276 Arrays.stream(new String[] { 277 "org.junit.Rule", 278 "org.junit.ClassRule", 279 "com.google.common.annotations.VisibleForTesting", 280 }).collect(Collectors.toList())); 281 282 /** Name for 'public' access modifier. */ 283 private static final String PUBLIC_ACCESS_MODIFIER = "public"; 284 285 /** Name for 'private' access modifier. */ 286 private static final String PRIVATE_ACCESS_MODIFIER = "private"; 287 288 /** Name for 'protected' access modifier. */ 289 private static final String PROTECTED_ACCESS_MODIFIER = "protected"; 290 291 /** Name for implicit 'package' access modifier. */ 292 private static final String PACKAGE_ACCESS_MODIFIER = "package"; 293 294 /** Name for 'static' keyword. */ 295 private static final String STATIC_KEYWORD = "static"; 296 297 /** Name for 'final' keyword. */ 298 private static final String FINAL_KEYWORD = "final"; 299 300 /** Contains explicit access modifiers. */ 301 private static final String[] EXPLICIT_MODS = { 302 PUBLIC_ACCESS_MODIFIER, 303 PRIVATE_ACCESS_MODIFIER, 304 PROTECTED_ACCESS_MODIFIER, 305 }; 306 307 /** Regexp for public members that should be ignored. Note: 308 * Earlier versions of checkstyle used ^f[A-Z][a-zA-Z0-9]*$ as the 309 * default to allow CMP for EJB 1.1 with the default settings. 310 * With EJB 2.0 it is not longer necessary to have public access 311 * for persistent fields. 312 */ 313 private Pattern publicMemberPattern = Pattern.compile("^serialVersionUID$"); 314 315 /** List of ignore annotations short names. */ 316 private final List<String> ignoreAnnotationShortNames = 317 getClassShortNames(DEFAULT_IGNORE_ANNOTATIONS); 318 319 /** List of immutable classes short names. */ 320 private final List<String> immutableClassShortNames = 321 getClassShortNames(DEFAULT_IMMUTABLE_TYPES); 322 323 /** List of ignore annotations canonical names. */ 324 private List<String> ignoreAnnotationCanonicalNames = 325 new ArrayList<>(DEFAULT_IGNORE_ANNOTATIONS); 326 327 /** Whether protected members are allowed. */ 328 private boolean protectedAllowed; 329 330 /** Whether package visible members are allowed. */ 331 private boolean packageAllowed; 332 333 /** Allows immutable fields of final classes to be declared as public. */ 334 private boolean allowPublicImmutableFields; 335 336 /** Allows final fields to be declared as public. */ 337 private boolean allowPublicFinalFields; 338 339 /** List of immutable classes canonical names. */ 340 private List<String> immutableClassCanonicalNames = new ArrayList<>(DEFAULT_IMMUTABLE_TYPES); 341 342 /** 343 * Set the list of ignore annotations. 344 * @param annotationNames array of ignore annotations canonical names. 345 */ 346 public void setIgnoreAnnotationCanonicalNames(String... annotationNames) { 347 ignoreAnnotationCanonicalNames = Arrays.asList(annotationNames); 348 } 349 350 /** 351 * Set whether protected members are allowed. 352 * @param protectedAllowed whether protected members are allowed 353 */ 354 public void setProtectedAllowed(boolean protectedAllowed) { 355 this.protectedAllowed = protectedAllowed; 356 } 357 358 /** 359 * Set whether package visible members are allowed. 360 * @param packageAllowed whether package visible members are allowed 361 */ 362 public void setPackageAllowed(boolean packageAllowed) { 363 this.packageAllowed = packageAllowed; 364 } 365 366 /** 367 * Set the pattern for public members to ignore. 368 * @param pattern 369 * pattern for public members to ignore. 370 */ 371 public void setPublicMemberPattern(Pattern pattern) { 372 publicMemberPattern = pattern; 373 } 374 375 /** 376 * Sets whether public immutable fields are allowed. 377 * @param allow user's value. 378 */ 379 public void setAllowPublicImmutableFields(boolean allow) { 380 allowPublicImmutableFields = allow; 381 } 382 383 /** 384 * Sets whether public final fields are allowed. 385 * @param allow user's value. 386 */ 387 public void setAllowPublicFinalFields(boolean allow) { 388 allowPublicFinalFields = allow; 389 } 390 391 /** 392 * Set the list of immutable classes types names. 393 * @param classNames array of immutable types canonical names. 394 */ 395 public void setImmutableClassCanonicalNames(String... classNames) { 396 immutableClassCanonicalNames = Arrays.asList(classNames); 397 } 398 399 @Override 400 public int[] getDefaultTokens() { 401 return getRequiredTokens(); 402 } 403 404 @Override 405 public int[] getAcceptableTokens() { 406 return getRequiredTokens(); 407 } 408 409 @Override 410 public int[] getRequiredTokens() { 411 return new int[] { 412 TokenTypes.VARIABLE_DEF, 413 TokenTypes.IMPORT, 414 }; 415 } 416 417 @Override 418 public void beginTree(DetailAST rootAst) { 419 immutableClassShortNames.clear(); 420 final List<String> classShortNames = 421 getClassShortNames(immutableClassCanonicalNames); 422 immutableClassShortNames.addAll(classShortNames); 423 424 ignoreAnnotationShortNames.clear(); 425 final List<String> annotationShortNames = 426 getClassShortNames(ignoreAnnotationCanonicalNames); 427 ignoreAnnotationShortNames.addAll(annotationShortNames); 428 } 429 430 @Override 431 public void visitToken(DetailAST ast) { 432 switch (ast.getType()) { 433 case TokenTypes.VARIABLE_DEF: 434 if (!isAnonymousClassVariable(ast)) { 435 visitVariableDef(ast); 436 } 437 break; 438 case TokenTypes.IMPORT: 439 visitImport(ast); 440 break; 441 default: 442 final String exceptionMsg = "Unexpected token type: " + ast.getText(); 443 throw new IllegalArgumentException(exceptionMsg); 444 } 445 } 446 447 /** 448 * Checks if current variable definition is definition of an anonymous class. 449 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 450 * @return true if current variable definition is definition of an anonymous class. 451 */ 452 private static boolean isAnonymousClassVariable(DetailAST variableDef) { 453 return variableDef.getParent().getType() != TokenTypes.OBJBLOCK; 454 } 455 456 /** 457 * Checks access modifier of given variable. 458 * If it is not proper according to Check - puts violation on it. 459 * @param variableDef variable to check. 460 */ 461 private void visitVariableDef(DetailAST variableDef) { 462 final boolean inInterfaceOrAnnotationBlock = 463 ScopeUtil.isInInterfaceOrAnnotationBlock(variableDef); 464 465 if (!inInterfaceOrAnnotationBlock && !hasIgnoreAnnotation(variableDef)) { 466 final DetailAST varNameAST = variableDef.findFirstToken(TokenTypes.TYPE) 467 .getNextSibling(); 468 final String varName = varNameAST.getText(); 469 if (!hasProperAccessModifier(variableDef, varName)) { 470 log(varNameAST, MSG_KEY, varName); 471 } 472 } 473 } 474 475 /** 476 * Checks if variable def has ignore annotation. 477 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 478 * @return true if variable def has ignore annotation. 479 */ 480 private boolean hasIgnoreAnnotation(DetailAST variableDef) { 481 final DetailAST firstIgnoreAnnotation = 482 findMatchingAnnotation(variableDef); 483 return firstIgnoreAnnotation != null; 484 } 485 486 /** 487 * Checks imported type. If type's canonical name was not specified in 488 * <b>immutableClassCanonicalNames</b>, but it's short name collides with one from 489 * <b>immutableClassShortNames</b> - removes it from the last one. 490 * @param importAst {@link TokenTypes#IMPORT Import} 491 */ 492 private void visitImport(DetailAST importAst) { 493 if (!isStarImport(importAst)) { 494 final DetailAST type = importAst.getFirstChild(); 495 final String canonicalName = getCanonicalName(type); 496 final String shortName = getClassShortName(canonicalName); 497 498 // If imported canonical class name is not specified as allowed immutable class, 499 // but its short name collides with one of specified class - removes the short name 500 // from list to avoid names collision 501 if (!immutableClassCanonicalNames.contains(canonicalName)) { 502 immutableClassShortNames.remove(shortName); 503 } 504 if (!ignoreAnnotationCanonicalNames.contains(canonicalName)) { 505 ignoreAnnotationShortNames.remove(shortName); 506 } 507 } 508 } 509 510 /** 511 * Checks if current import is star import. E.g.: 512 * <p> 513 * {@code 514 * import java.util.*; 515 * } 516 * </p> 517 * @param importAst {@link TokenTypes#IMPORT Import} 518 * @return true if it is star import 519 */ 520 private static boolean isStarImport(DetailAST importAst) { 521 boolean result = false; 522 DetailAST toVisit = importAst; 523 while (toVisit != null) { 524 toVisit = getNextSubTreeNode(toVisit, importAst); 525 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) { 526 result = true; 527 break; 528 } 529 } 530 return result; 531 } 532 533 /** 534 * Checks if current variable has proper access modifier according to Check's options. 535 * @param variableDef Variable definition node. 536 * @param variableName Variable's name. 537 * @return true if variable has proper access modifier. 538 */ 539 private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) { 540 boolean result = true; 541 542 final String variableScope = getVisibilityScope(variableDef); 543 544 if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) { 545 result = 546 isStaticFinalVariable(variableDef) 547 || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope) 548 || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope) 549 || isIgnoredPublicMember(variableName, variableScope) 550 || isAllowedPublicField(variableDef); 551 } 552 553 return result; 554 } 555 556 /** 557 * Checks whether variable has static final modifiers. 558 * @param variableDef Variable definition node. 559 * @return true of variable has static final modifiers. 560 */ 561 private static boolean isStaticFinalVariable(DetailAST variableDef) { 562 final Set<String> modifiers = getModifiers(variableDef); 563 return modifiers.contains(STATIC_KEYWORD) 564 && modifiers.contains(FINAL_KEYWORD); 565 } 566 567 /** 568 * Checks whether variable belongs to public members that should be ignored. 569 * @param variableName Variable's name. 570 * @param variableScope Variable's scope. 571 * @return true if variable belongs to public members that should be ignored. 572 */ 573 private boolean isIgnoredPublicMember(String variableName, String variableScope) { 574 return PUBLIC_ACCESS_MODIFIER.equals(variableScope) 575 && publicMemberPattern.matcher(variableName).find(); 576 } 577 578 /** 579 * Checks whether the variable satisfies the public field check. 580 * @param variableDef Variable definition node. 581 * @return true if allowed. 582 */ 583 private boolean isAllowedPublicField(DetailAST variableDef) { 584 return allowPublicFinalFields && isFinalField(variableDef) 585 || allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef); 586 } 587 588 /** 589 * Checks whether immutable field is defined in final class. 590 * @param variableDef Variable definition node. 591 * @return true if immutable field is defined in final class. 592 */ 593 private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) { 594 final DetailAST classDef = variableDef.getParent().getParent(); 595 final Set<String> classModifiers = getModifiers(classDef); 596 return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF) 597 && isImmutableField(variableDef); 598 } 599 600 /** 601 * Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST. 602 * @param defAST AST for a variable or class definition. 603 * @return the set of modifier Strings for defAST. 604 */ 605 private static Set<String> getModifiers(DetailAST defAST) { 606 final AST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS); 607 final Set<String> modifiersSet = new HashSet<>(); 608 if (modifiersAST != null) { 609 AST modifier = modifiersAST.getFirstChild(); 610 while (modifier != null) { 611 modifiersSet.add(modifier.getText()); 612 modifier = modifier.getNextSibling(); 613 } 614 } 615 return modifiersSet; 616 } 617 618 /** 619 * Returns the visibility scope for the variable. 620 * @param variableDef Variable definition node. 621 * @return one of "public", "private", "protected", "package" 622 */ 623 private static String getVisibilityScope(DetailAST variableDef) { 624 final Set<String> modifiers = getModifiers(variableDef); 625 String accessModifier = PACKAGE_ACCESS_MODIFIER; 626 for (final String modifier : EXPLICIT_MODS) { 627 if (modifiers.contains(modifier)) { 628 accessModifier = modifier; 629 break; 630 } 631 } 632 return accessModifier; 633 } 634 635 /** 636 * Checks if current field is immutable: 637 * has final modifier and either a primitive type or instance of class 638 * known to be immutable (such as String, ImmutableCollection from Guava and etc). 639 * Classes known to be immutable are listed in 640 * {@link VisibilityModifierCheck#immutableClassCanonicalNames} 641 * @param variableDef Field in consideration. 642 * @return true if field is immutable. 643 */ 644 private boolean isImmutableField(DetailAST variableDef) { 645 boolean result = false; 646 if (isFinalField(variableDef)) { 647 final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE); 648 final boolean isCanonicalName = isCanonicalName(type); 649 final String typeName = getTypeName(type, isCanonicalName); 650 if (immutableClassShortNames.contains(typeName) 651 || isCanonicalName && immutableClassCanonicalNames.contains(typeName)) { 652 final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName); 653 654 if (typeArgs == null) { 655 result = true; 656 } 657 else { 658 final List<String> argsClassNames = getTypeArgsClassNames(typeArgs); 659 result = areImmutableTypeArguments(argsClassNames); 660 } 661 } 662 else { 663 result = !isCanonicalName && isPrimitive(type); 664 } 665 } 666 return result; 667 } 668 669 /** 670 * Checks whether type definition is in canonical form. 671 * @param type type definition token. 672 * @return true if type definition is in canonical form. 673 */ 674 private static boolean isCanonicalName(DetailAST type) { 675 return type.getFirstChild().getType() == TokenTypes.DOT; 676 } 677 678 /** 679 * Returns generic type arguments token. 680 * @param type type token. 681 * @param isCanonicalName whether type name is in canonical form. 682 * @return generic type arguments token. 683 */ 684 private static DetailAST getGenericTypeArgs(DetailAST type, boolean isCanonicalName) { 685 final DetailAST typeArgs; 686 if (isCanonicalName) { 687 // if type class name is in canonical form, abstract tree has specific structure 688 typeArgs = type.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS); 689 } 690 else { 691 typeArgs = type.findFirstToken(TokenTypes.TYPE_ARGUMENTS); 692 } 693 return typeArgs; 694 } 695 696 /** 697 * Returns a list of type parameters class names. 698 * @param typeArgs type arguments token. 699 * @return a list of type parameters class names. 700 */ 701 private static List<String> getTypeArgsClassNames(DetailAST typeArgs) { 702 final List<String> typeClassNames = new ArrayList<>(); 703 DetailAST type = typeArgs.findFirstToken(TokenTypes.TYPE_ARGUMENT); 704 boolean isCanonicalName = isCanonicalName(type); 705 String typeName = getTypeName(type, isCanonicalName); 706 typeClassNames.add(typeName); 707 DetailAST sibling = type.getNextSibling(); 708 while (sibling.getType() == TokenTypes.COMMA) { 709 type = sibling.getNextSibling(); 710 isCanonicalName = isCanonicalName(type); 711 typeName = getTypeName(type, isCanonicalName); 712 typeClassNames.add(typeName); 713 sibling = type.getNextSibling(); 714 } 715 return typeClassNames; 716 } 717 718 /** 719 * Checks whether all of generic type arguments are immutable. 720 * If at least one argument is mutable, we assume that the whole list of type arguments 721 * is mutable. 722 * @param typeArgsClassNames type arguments class names. 723 * @return true if all of generic type arguments are immutable. 724 */ 725 private boolean areImmutableTypeArguments(List<String> typeArgsClassNames) { 726 return typeArgsClassNames.stream().noneMatch( 727 typeName -> { 728 return !immutableClassShortNames.contains(typeName) 729 && !immutableClassCanonicalNames.contains(typeName); 730 }); 731 } 732 733 /** 734 * Checks whether current field is final. 735 * @param variableDef field in consideration. 736 * @return true if current field is final. 737 */ 738 private static boolean isFinalField(DetailAST variableDef) { 739 final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS); 740 return modifiers.findFirstToken(TokenTypes.FINAL) != null; 741 } 742 743 /** 744 * Gets the name of type from given ast {@link TokenTypes#TYPE TYPE} node. 745 * If type is specified via its canonical name - canonical name will be returned, 746 * else - short type's name. 747 * @param type {@link TokenTypes#TYPE TYPE} node. 748 * @param isCanonicalName is given name canonical. 749 * @return String representation of given type's name. 750 */ 751 private static String getTypeName(DetailAST type, boolean isCanonicalName) { 752 final String typeName; 753 if (isCanonicalName) { 754 typeName = getCanonicalName(type); 755 } 756 else { 757 typeName = type.getFirstChild().getText(); 758 } 759 return typeName; 760 } 761 762 /** 763 * Checks if current type is primitive type (int, short, float, boolean, double, etc.). 764 * As primitive types have special tokens for each one, such as: 765 * LITERAL_INT, LITERAL_BOOLEAN, etc. 766 * So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a 767 * primitive type. 768 * @param type Ast {@link TokenTypes#TYPE TYPE} node. 769 * @return true if current type is primitive type. 770 */ 771 private static boolean isPrimitive(DetailAST type) { 772 return type.getFirstChild().getType() != TokenTypes.IDENT; 773 } 774 775 /** 776 * Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node. 777 * @param type DetailAST {@link TokenTypes#TYPE TYPE} node. 778 * @return canonical type's name 779 */ 780 private static String getCanonicalName(DetailAST type) { 781 final StringBuilder canonicalNameBuilder = new StringBuilder(256); 782 DetailAST toVisit = type.getFirstChild(); 783 while (toVisit != null) { 784 toVisit = getNextSubTreeNode(toVisit, type); 785 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) { 786 if (canonicalNameBuilder.length() > 0) { 787 canonicalNameBuilder.append('.'); 788 } 789 canonicalNameBuilder.append(toVisit.getText()); 790 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type); 791 if (nextSubTreeNode != null 792 && nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) { 793 break; 794 } 795 } 796 } 797 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 DetailAST currentNode = currentNodeAst; 811 DetailAST toVisitAst = currentNode.getFirstChild(); 812 while (toVisitAst == null) { 813 toVisitAst = currentNode.getNextSibling(); 814 if (currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) { 815 break; 816 } 817 currentNode = currentNode.getParent(); 818 } 819 return toVisitAst; 820 } 821 822 /** 823 * Gets the list with short names classes. 824 * These names are taken from array of classes canonical names. 825 * @param canonicalClassNames canonical class names. 826 * @return the list of short names of classes. 827 */ 828 private static List<String> getClassShortNames(List<String> canonicalClassNames) { 829 final List<String> shortNames = new ArrayList<>(); 830 for (String canonicalClassName : canonicalClassNames) { 831 final String shortClassName = canonicalClassName 832 .substring(canonicalClassName.lastIndexOf('.') + 1); 833 shortNames.add(shortClassName); 834 } 835 return shortNames; 836 } 837 838 /** 839 * Gets the short class name from given canonical name. 840 * @param canonicalClassName canonical class name. 841 * @return short name of class. 842 */ 843 private static String getClassShortName(String canonicalClassName) { 844 return canonicalClassName 845 .substring(canonicalClassName.lastIndexOf('.') + 1); 846 } 847 848 /** 849 * Checks whether the AST is annotated with 850 * an annotation containing the passed in regular 851 * expression and return the AST representing that 852 * annotation. 853 * 854 * <p> 855 * This method will not look for imports or package 856 * statements to detect the passed in annotation. 857 * </p> 858 * 859 * <p> 860 * To check if an AST contains a passed in annotation 861 * taking into account fully-qualified names 862 * (ex: java.lang.Override, Override) 863 * this method will need to be called twice. Once for each 864 * name given. 865 * </p> 866 * 867 * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}. 868 * @return the AST representing the first such annotation or null if 869 * no such annotation was found 870 */ 871 private DetailAST findMatchingAnnotation(DetailAST variableDef) { 872 DetailAST matchingAnnotation = null; 873 874 final DetailAST holder = AnnotationUtil.getAnnotationHolder(variableDef); 875 876 for (DetailAST child = holder.getFirstChild(); 877 child != null; child = child.getNextSibling()) { 878 if (child.getType() == TokenTypes.ANNOTATION) { 879 final DetailAST ast = child.getFirstChild(); 880 final String name = 881 FullIdent.createFullIdent(ast.getNextSibling()).getText(); 882 if (ignoreAnnotationCanonicalNames.contains(name) 883 || ignoreAnnotationShortNames.contains(name)) { 884 matchingAnnotation = child; 885 break; 886 } 887 } 888 } 889 890 return matchingAnnotation; 891 } 892 893}