001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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 final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName); 651 if (typeArgs == null) { 652 result = !isCanonicalName && isPrimitive(type) 653 || immutableClassShortNames.contains(typeName) 654 || isCanonicalName && immutableClassCanonicalNames.contains(typeName); 655 } 656 else { 657 final List<String> argsClassNames = getTypeArgsClassNames(typeArgs); 658 result = (immutableClassShortNames.contains(typeName) 659 || isCanonicalName && immutableClassCanonicalNames.contains(typeName)) 660 && areImmutableTypeArguments(argsClassNames); 661 } 662 } 663 return result; 664 } 665 666 /** 667 * Checks whether type definition is in canonical form. 668 * @param type type definition token. 669 * @return true if type definition is in canonical form. 670 */ 671 private static boolean isCanonicalName(DetailAST type) { 672 return type.getFirstChild().getType() == TokenTypes.DOT; 673 } 674 675 /** 676 * Returns generic type arguments token. 677 * @param type type token. 678 * @param isCanonicalName whether type name is in canonical form. 679 * @return generic type arguments token. 680 */ 681 private static DetailAST getGenericTypeArgs(DetailAST type, boolean isCanonicalName) { 682 final DetailAST typeArgs; 683 if (isCanonicalName) { 684 // if type class name is in canonical form, abstract tree has specific structure 685 typeArgs = type.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS); 686 } 687 else { 688 typeArgs = type.findFirstToken(TokenTypes.TYPE_ARGUMENTS); 689 } 690 return typeArgs; 691 } 692 693 /** 694 * Returns a list of type parameters class names. 695 * @param typeArgs type arguments token. 696 * @return a list of type parameters class names. 697 */ 698 private static List<String> getTypeArgsClassNames(DetailAST typeArgs) { 699 final List<String> typeClassNames = new ArrayList<>(); 700 DetailAST type = typeArgs.findFirstToken(TokenTypes.TYPE_ARGUMENT); 701 boolean isCanonicalName = isCanonicalName(type); 702 String typeName = getTypeName(type, isCanonicalName); 703 typeClassNames.add(typeName); 704 DetailAST sibling = type.getNextSibling(); 705 while (sibling.getType() == TokenTypes.COMMA) { 706 type = sibling.getNextSibling(); 707 isCanonicalName = isCanonicalName(type); 708 typeName = getTypeName(type, isCanonicalName); 709 typeClassNames.add(typeName); 710 sibling = type.getNextSibling(); 711 } 712 return typeClassNames; 713 } 714 715 /** 716 * Checks whether all of generic type arguments are immutable. 717 * If at least one argument is mutable, we assume that the whole list of type arguments 718 * is mutable. 719 * @param typeArgsClassNames type arguments class names. 720 * @return true if all of generic type arguments are immutable. 721 */ 722 private boolean areImmutableTypeArguments(List<String> typeArgsClassNames) { 723 return typeArgsClassNames.stream().noneMatch( 724 typeName -> { 725 return !immutableClassShortNames.contains(typeName) 726 && !immutableClassCanonicalNames.contains(typeName); 727 }); 728 } 729 730 /** 731 * Checks whether current field is final. 732 * @param variableDef field in consideration. 733 * @return true if current field is final. 734 */ 735 private static boolean isFinalField(DetailAST variableDef) { 736 final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS); 737 return modifiers.findFirstToken(TokenTypes.FINAL) != null; 738 } 739 740 /** 741 * Gets the name of type from given ast {@link TokenTypes#TYPE TYPE} node. 742 * If type is specified via its canonical name - canonical name will be returned, 743 * else - short type's name. 744 * @param type {@link TokenTypes#TYPE TYPE} node. 745 * @param isCanonicalName is given name canonical. 746 * @return String representation of given type's name. 747 */ 748 private static String getTypeName(DetailAST type, boolean isCanonicalName) { 749 final String typeName; 750 if (isCanonicalName) { 751 typeName = getCanonicalName(type); 752 } 753 else { 754 typeName = type.getFirstChild().getText(); 755 } 756 return typeName; 757 } 758 759 /** 760 * Checks if current type is primitive type (int, short, float, boolean, double, etc.). 761 * As primitive types have special tokens for each one, such as: 762 * LITERAL_INT, LITERAL_BOOLEAN, etc. 763 * So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a 764 * primitive type. 765 * @param type Ast {@link TokenTypes#TYPE TYPE} node. 766 * @return true if current type is primitive type. 767 */ 768 private static boolean isPrimitive(DetailAST type) { 769 return type.getFirstChild().getType() != TokenTypes.IDENT; 770 } 771 772 /** 773 * Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node. 774 * @param type DetailAST {@link TokenTypes#TYPE TYPE} node. 775 * @return canonical type's name 776 */ 777 private static String getCanonicalName(DetailAST type) { 778 final StringBuilder canonicalNameBuilder = new StringBuilder(256); 779 DetailAST toVisit = type.getFirstChild(); 780 while (toVisit != null) { 781 toVisit = getNextSubTreeNode(toVisit, type); 782 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) { 783 canonicalNameBuilder.append(toVisit.getText()); 784 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type); 785 if (nextSubTreeNode != null) { 786 if (nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) { 787 break; 788 } 789 canonicalNameBuilder.append('.'); 790 } 791 } 792 } 793 return canonicalNameBuilder.toString(); 794 } 795 796 /** 797 * Gets the next node of a syntactical tree (child of a current node or 798 * sibling of a current node, or sibling of a parent of a current node). 799 * @param currentNodeAst Current node in considering 800 * @param subTreeRootAst SubTree root 801 * @return Current node after bypassing, if current node reached the root of a subtree 802 * method returns null 803 */ 804 private static DetailAST 805 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) { 806 DetailAST currentNode = currentNodeAst; 807 DetailAST toVisitAst = currentNode.getFirstChild(); 808 while (toVisitAst == null) { 809 toVisitAst = currentNode.getNextSibling(); 810 if (toVisitAst == null) { 811 if (currentNode.getParent().equals(subTreeRootAst) 812 && currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) { 813 break; 814 } 815 currentNode = currentNode.getParent(); 816 } 817 } 818 return toVisitAst; 819 } 820 821 /** 822 * Gets the list with short names classes. 823 * These names are taken from array of classes canonical names. 824 * @param canonicalClassNames canonical class names. 825 * @return the list of short names of classes. 826 */ 827 private static List<String> getClassShortNames(List<String> canonicalClassNames) { 828 final List<String> shortNames = new ArrayList<>(); 829 for (String canonicalClassName : canonicalClassNames) { 830 final String shortClassName = canonicalClassName 831 .substring(canonicalClassName.lastIndexOf('.') + 1, 832 canonicalClassName.length()); 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 canonicalClassName.length()); 847 } 848 849 /** 850 * Checks whether the AST is annotated with 851 * an annotation containing the passed in regular 852 * expression and return the AST representing that 853 * annotation. 854 * 855 * <p> 856 * This method will not look for imports or package 857 * statements to detect the passed in annotation. 858 * </p> 859 * 860 * <p> 861 * To check if an AST contains a passed in annotation 862 * taking into account fully-qualified names 863 * (ex: java.lang.Override, Override) 864 * this method will need to be called twice. Once for each 865 * name given. 866 * </p> 867 * 868 * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}. 869 * @return the AST representing the first such annotation or null if 870 * no such annotation was found 871 */ 872 private DetailAST findMatchingAnnotation(DetailAST variableDef) { 873 DetailAST matchingAnnotation = null; 874 875 final DetailAST holder = AnnotationUtil.getAnnotationHolder(variableDef); 876 877 for (DetailAST child = holder.getFirstChild(); 878 child != null; child = child.getNextSibling()) { 879 if (child.getType() == TokenTypes.ANNOTATION) { 880 final DetailAST ast = child.getFirstChild(); 881 final String name = 882 FullIdent.createFullIdent(ast.getNextSibling()).getText(); 883 if (ignoreAnnotationCanonicalNames.contains(name) 884 || ignoreAnnotationShortNames.contains(name)) { 885 matchingAnnotation = child; 886 break; 887 } 888 } 889 } 890 891 return matchingAnnotation; 892 } 893 894}