001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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; 030import java.util.stream.Stream; 031 032import antlr.collections.AST; 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.AnnotationUtility; 038import com.puppycrawl.tools.checkstyle.utils.ScopeUtils; 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 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 239 */ 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 Stream.of( 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 Stream.of( 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 getAcceptableTokens(); 402 } 403 404 @Override 405 public int[] getAcceptableTokens() { 406 return new int[] { 407 TokenTypes.VARIABLE_DEF, 408 TokenTypes.IMPORT, 409 }; 410 } 411 412 @Override 413 public int[] getRequiredTokens() { 414 return getAcceptableTokens(); 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 ScopeUtils.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.getLineNo(), varNameAST.getColumnNo(), 471 MSG_KEY, varName); 472 } 473 } 474 } 475 476 /** 477 * Checks if variable def has ignore annotation. 478 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 479 * @return true if variable def has ignore annotation. 480 */ 481 private boolean hasIgnoreAnnotation(DetailAST variableDef) { 482 final DetailAST firstIgnoreAnnotation = 483 findMatchingAnnotation(variableDef); 484 return firstIgnoreAnnotation != null; 485 } 486 487 /** 488 * Checks imported type. If type's canonical name was not specified in 489 * <b>immutableClassCanonicalNames</b>, but it's short name collides with one from 490 * <b>immutableClassShortNames</b> - removes it from the last one. 491 * @param importAst {@link TokenTypes#IMPORT Import} 492 */ 493 private void visitImport(DetailAST importAst) { 494 if (!isStarImport(importAst)) { 495 final DetailAST type = importAst.getFirstChild(); 496 final String canonicalName = getCanonicalName(type); 497 final String shortName = getClassShortName(canonicalName); 498 499 // If imported canonical class name is not specified as allowed immutable class, 500 // but its short name collides with one of specified class - removes the short name 501 // from list to avoid names collision 502 if (!immutableClassCanonicalNames.contains(canonicalName) 503 && immutableClassShortNames.contains(shortName)) { 504 immutableClassShortNames.remove(shortName); 505 } 506 if (!ignoreAnnotationCanonicalNames.contains(canonicalName) 507 && ignoreAnnotationShortNames.contains(shortName)) { 508 ignoreAnnotationShortNames.remove(shortName); 509 } 510 } 511 } 512 513 /** 514 * Checks if current import is star import. E.g.: 515 * <p> 516 * {@code 517 * import java.util.*; 518 * } 519 * </p> 520 * @param importAst {@link TokenTypes#IMPORT Import} 521 * @return true if it is star import 522 */ 523 private static boolean isStarImport(DetailAST importAst) { 524 boolean result = false; 525 DetailAST toVisit = importAst; 526 while (toVisit != null) { 527 toVisit = getNextSubTreeNode(toVisit, importAst); 528 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) { 529 result = true; 530 break; 531 } 532 } 533 return result; 534 } 535 536 /** 537 * Checks if current variable has proper access modifier according to Check's options. 538 * @param variableDef Variable definition node. 539 * @param variableName Variable's name. 540 * @return true if variable has proper access modifier. 541 */ 542 private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) { 543 boolean result = true; 544 545 final String variableScope = getVisibilityScope(variableDef); 546 547 if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) { 548 result = 549 isStaticFinalVariable(variableDef) 550 || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope) 551 || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope) 552 || isIgnoredPublicMember(variableName, variableScope) 553 || isAllowedPublicField(variableDef); 554 } 555 556 return result; 557 } 558 559 /** 560 * Checks whether variable has static final modifiers. 561 * @param variableDef Variable definition node. 562 * @return true of variable has static final modifiers. 563 */ 564 private static boolean isStaticFinalVariable(DetailAST variableDef) { 565 final Set<String> modifiers = getModifiers(variableDef); 566 return modifiers.contains(STATIC_KEYWORD) 567 && modifiers.contains(FINAL_KEYWORD); 568 } 569 570 /** 571 * Checks whether variable belongs to public members that should be ignored. 572 * @param variableName Variable's name. 573 * @param variableScope Variable's scope. 574 * @return true if variable belongs to public members that should be ignored. 575 */ 576 private boolean isIgnoredPublicMember(String variableName, String variableScope) { 577 return PUBLIC_ACCESS_MODIFIER.equals(variableScope) 578 && publicMemberPattern.matcher(variableName).find(); 579 } 580 581 /** 582 * Checks whether the variable satisfies the public field check. 583 * @param variableDef Variable definition node. 584 * @return true if allowed. 585 */ 586 private boolean isAllowedPublicField(DetailAST variableDef) { 587 return allowPublicFinalFields && isFinalField(variableDef) 588 || allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef); 589 } 590 591 /** 592 * Checks whether immutable field is defined in final class. 593 * @param variableDef Variable definition node. 594 * @return true if immutable field is defined in final class. 595 */ 596 private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) { 597 final DetailAST classDef = variableDef.getParent().getParent(); 598 final Set<String> classModifiers = getModifiers(classDef); 599 return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF) 600 && isImmutableField(variableDef); 601 } 602 603 /** 604 * Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST. 605 * @param defAST AST for a variable or class definition. 606 * @return the set of modifier Strings for defAST. 607 */ 608 private static Set<String> getModifiers(DetailAST defAST) { 609 final AST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS); 610 final Set<String> modifiersSet = new HashSet<>(); 611 if (modifiersAST != null) { 612 AST modifier = modifiersAST.getFirstChild(); 613 while (modifier != null) { 614 modifiersSet.add(modifier.getText()); 615 modifier = modifier.getNextSibling(); 616 } 617 } 618 return modifiersSet; 619 } 620 621 /** 622 * Returns the visibility scope for the variable. 623 * @param variableDef Variable definition node. 624 * @return one of "public", "private", "protected", "package" 625 */ 626 private static String getVisibilityScope(DetailAST variableDef) { 627 final Set<String> modifiers = getModifiers(variableDef); 628 String accessModifier = PACKAGE_ACCESS_MODIFIER; 629 for (final String modifier : EXPLICIT_MODS) { 630 if (modifiers.contains(modifier)) { 631 accessModifier = modifier; 632 break; 633 } 634 } 635 return accessModifier; 636 } 637 638 /** 639 * Checks if current field is immutable: 640 * has final modifier and either a primitive type or instance of class 641 * known to be immutable (such as String, ImmutableCollection from Guava and etc). 642 * Classes known to be immutable are listed in 643 * {@link VisibilityModifierCheck#immutableClassCanonicalNames} 644 * @param variableDef Field in consideration. 645 * @return true if field is immutable. 646 */ 647 private boolean isImmutableField(DetailAST variableDef) { 648 boolean result = false; 649 if (isFinalField(variableDef)) { 650 final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE); 651 final boolean isCanonicalName = isCanonicalName(type); 652 final String typeName = getTypeName(type, isCanonicalName); 653 final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName); 654 if (typeArgs == null) { 655 result = !isCanonicalName && isPrimitive(type) 656 || immutableClassShortNames.contains(typeName) 657 || isCanonicalName && immutableClassCanonicalNames.contains(typeName); 658 } 659 else { 660 final List<String> argsClassNames = getTypeArgsClassNames(typeArgs); 661 result = (immutableClassShortNames.contains(typeName) 662 || isCanonicalName && immutableClassCanonicalNames.contains(typeName)) 663 && areImmutableTypeArguments(argsClassNames); 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().filter( 727 typeName -> !immutableClassShortNames.contains(typeName) 728 && !immutableClassCanonicalNames.contains(typeName)).findFirst().isPresent(); 729 } 730 731 /** 732 * Checks whether current field is final. 733 * @param variableDef field in consideration. 734 * @return true if current field is final. 735 */ 736 private static boolean isFinalField(DetailAST variableDef) { 737 final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS); 738 return modifiers.branchContains(TokenTypes.FINAL); 739 } 740 741 /** 742 * Gets the name of type from given ast {@link TokenTypes#TYPE TYPE} node. 743 * If type is specified via its canonical name - canonical name will be returned, 744 * else - short type's name. 745 * @param type {@link TokenTypes#TYPE TYPE} node. 746 * @param isCanonicalName is given name canonical. 747 * @return String representation of given type's name. 748 */ 749 private static String getTypeName(DetailAST type, boolean isCanonicalName) { 750 final String typeName; 751 if (isCanonicalName) { 752 typeName = getCanonicalName(type); 753 } 754 else { 755 typeName = type.getFirstChild().getText(); 756 } 757 return typeName; 758 } 759 760 /** 761 * Checks if current type is primitive type (int, short, float, boolean, double, etc.). 762 * As primitive types have special tokens for each one, such as: 763 * LITERAL_INT, LITERAL_BOOLEAN, etc. 764 * So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a 765 * primitive type. 766 * @param type Ast {@link TokenTypes#TYPE TYPE} node. 767 * @return true if current type is primitive type. 768 */ 769 private static boolean isPrimitive(DetailAST type) { 770 return type.getFirstChild().getType() != TokenTypes.IDENT; 771 } 772 773 /** 774 * Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node. 775 * @param type DetailAST {@link TokenTypes#TYPE TYPE} node. 776 * @return canonical type's name 777 */ 778 private static String getCanonicalName(DetailAST type) { 779 final StringBuilder canonicalNameBuilder = new StringBuilder(); 780 DetailAST toVisit = type.getFirstChild(); 781 while (toVisit != null) { 782 toVisit = getNextSubTreeNode(toVisit, type); 783 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) { 784 canonicalNameBuilder.append(toVisit.getText()); 785 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type); 786 if (nextSubTreeNode != null) { 787 if (nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) { 788 break; 789 } 790 canonicalNameBuilder.append('.'); 791 } 792 } 793 } 794 return canonicalNameBuilder.toString(); 795 } 796 797 /** 798 * Gets the next node of a syntactical tree (child of a current node or 799 * sibling of a current node, or sibling of a parent of a current node). 800 * @param currentNodeAst Current node in considering 801 * @param subTreeRootAst SubTree root 802 * @return Current node after bypassing, if current node reached the root of a subtree 803 * method returns null 804 */ 805 private static DetailAST 806 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) { 807 DetailAST currentNode = currentNodeAst; 808 DetailAST toVisitAst = currentNode.getFirstChild(); 809 while (toVisitAst == null) { 810 toVisitAst = currentNode.getNextSibling(); 811 if (toVisitAst == null) { 812 if (currentNode.getParent().equals(subTreeRootAst) 813 && currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) { 814 break; 815 } 816 currentNode = currentNode.getParent(); 817 } 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 canonicalClassName.length()); 834 shortNames.add(shortClassName); 835 } 836 return shortNames; 837 } 838 839 /** 840 * Gets the short class name from given canonical name. 841 * @param canonicalClassName canonical class name. 842 * @return short name of class. 843 */ 844 private static String getClassShortName(String canonicalClassName) { 845 return canonicalClassName 846 .substring(canonicalClassName.lastIndexOf('.') + 1, 847 canonicalClassName.length()); 848 } 849 850 /** 851 * Checks whether the AST is annotated with 852 * an annotation containing the passed in regular 853 * expression and return the AST representing that 854 * annotation. 855 * 856 * <p> 857 * This method will not look for imports or package 858 * statements to detect the passed in annotation. 859 * </p> 860 * 861 * <p> 862 * To check if an AST contains a passed in annotation 863 * taking into account fully-qualified names 864 * (ex: java.lang.Override, Override) 865 * this method will need to be called twice. Once for each 866 * name given. 867 * </p> 868 * 869 * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}. 870 * @return the AST representing the first such annotation or null if 871 * no such annotation was found 872 */ 873 private DetailAST findMatchingAnnotation(DetailAST variableDef) { 874 DetailAST matchingAnnotation = null; 875 876 final DetailAST holder = AnnotationUtility.getAnnotationHolder(variableDef); 877 878 for (DetailAST child = holder.getFirstChild(); 879 child != null; child = child.getNextSibling()) { 880 if (child.getType() == TokenTypes.ANNOTATION) { 881 final DetailAST ast = child.getFirstChild(); 882 final String name = 883 FullIdent.createFullIdent(ast.getNextSibling()).getText(); 884 if (ignoreAnnotationCanonicalNames.contains(name) 885 || ignoreAnnotationShortNames.contains(name)) { 886 matchingAnnotation = child; 887 break; 888 } 889 } 890 } 891 892 return matchingAnnotation; 893 } 894}