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; 030 031import antlr.collections.AST; 032import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 033import com.puppycrawl.tools.checkstyle.api.DetailAST; 034import com.puppycrawl.tools.checkstyle.api.FullIdent; 035import com.puppycrawl.tools.checkstyle.api.TokenTypes; 036import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility; 037import com.puppycrawl.tools.checkstyle.utils.ScopeUtils; 038 039/** 040 * Checks visibility of class members. Only static final, immutable or annotated 041 * by specified annotation members may be public, 042 * other class members must be private unless allowProtected/Package is set. 043 * <p> 044 * Public members are not flagged if the name matches the public 045 * member regular expression (contains "^serialVersionUID$" by 046 * default). 047 * </p> 048 * Rationale: Enforce encapsulation. 049 * <p> 050 * Check also has options making it less strict: 051 * </p> 052 * <p> 053 * <b>ignoreAnnotationCanonicalNames</b> - the list of annotations canonical names 054 * which ignore variables in consideration, if user will provide short annotation name 055 * that type will match to any named the same type without consideration of package, 056 * list by default: 057 * </p> 058 * <ul> 059 * <li>org.junit.Rule</li> 060 * <li>org.junit.ClassRule</li> 061 * <li>com.google.common.annotations.VisibleForTesting</li> 062 * </ul> 063 * <p> 064 * For example such public field will be skipped by default value of list above: 065 * </p> 066 * 067 * <pre> 068 * {@code @org.junit.Rule 069 * public TemporaryFolder publicJUnitRule = new TemporaryFolder(); 070 * } 071 * </pre> 072 * 073 * <p> 074 * <b>allowPublicFinalFields</b> - which allows public final fields. Default value is <b>false</b>. 075 * </p> 076 * <p> 077 * <b>allowPublicImmutableFields</b> - which allows immutable fields to be 078 * declared as public if defined in final class. Default value is <b>false</b> 079 * </p> 080 * <p> 081 * Field is known to be immutable if: 082 * </p> 083 * <ul> 084 * <li>It's declared as final</li> 085 * <li>Has either a primitive type or instance of class user defined to be immutable 086 * (such as String, ImmutableCollection from Guava and etc)</li> 087 * </ul> 088 * <p> 089 * Classes known to be immutable are listed in <b>immutableClassCanonicalNames</b> by their 090 * <b>canonical</b> names. List by default: 091 * </p> 092 * <ul> 093 * <li>java.lang.String</li> 094 * <li>java.lang.Integer</li> 095 * <li>java.lang.Byte</li> 096 * <li>java.lang.Character</li> 097 * <li>java.lang.Short</li> 098 * <li>java.lang.Boolean</li> 099 * <li>java.lang.Long</li> 100 * <li>java.lang.Double</li> 101 * <li>java.lang.Float</li> 102 * <li>java.lang.StackTraceElement</li> 103 * <li>java.lang.BigInteger</li> 104 * <li>java.lang.BigDecimal</li> 105 * <li>java.io.File</li> 106 * <li>java.util.Locale</li> 107 * <li>java.util.UUID</li> 108 * <li>java.net.URL</li> 109 * <li>java.net.URI</li> 110 * <li>java.net.Inet4Address</li> 111 * <li>java.net.Inet6Address</li> 112 * <li>java.net.InetSocketAddress</li> 113 * </ul> 114 * <p> 115 * User can override this list via adding <b>canonical</b> class names to 116 * <b>immutableClassCanonicalNames</b>, if user will provide short class name all 117 * that type will match to any named the same type without consideration of package. 118 * </p> 119 * <p> 120 * <b>Rationale</b>: Forcing all fields of class to have private modified by default is good 121 * in most cases, but in some cases it drawbacks in too much boilerplate get/set code. 122 * One of such cases are immutable classes. 123 * </p> 124 * <p> 125 * <b>Restriction</b>: Check doesn't check if class is immutable, there's no checking 126 * if accessory methods are missing and all fields are immutable, we only check 127 * <b>if current field is immutable by matching a name to user defined list of immutable classes 128 * and defined in final class</b> 129 * </p> 130 * <p> 131 * Star imports are out of scope of this Check. So if one of type imported via <b>star import</b> 132 * collides with user specified one by its short name - there won't be Check's violation. 133 * </p> 134 * Examples: 135 * <p> 136 * The check will rise 3 violations if it is run with default configuration against the following 137 * code example: 138 * </p> 139 * 140 * <pre> 141 * {@code 142 * public class ImmutableClass 143 * { 144 * public int intValue; // violation 145 * public java.lang.String notes; // violation 146 * public BigDecimal value; // violation 147 * 148 * public ImmutableClass(int intValue, BigDecimal value, String notes) 149 * { 150 * this.intValue = intValue; 151 * this.value = value; 152 * this.notes = notes; 153 * } 154 * } 155 * } 156 * </pre> 157 * 158 * <p> 159 * To configure the Check passing fields of type com.google.common.collect.ImmutableSet and 160 * java.util.List: 161 * </p> 162 * <p> 163 * <module name="VisibilityModifier"> 164 * <property name="allowPublicImmutableFields" value="true"/> 165 * <property name="immutableClassCanonicalNames" value="java.util.List, 166 * com.google.common.collect.ImmutableSet"/> 167 * </module> 168 * </p> 169 * 170 * <pre> 171 * {@code 172 * public final class ImmutableClass 173 * { 174 * public final ImmutableSet<String> includes; // No warning 175 * public final ImmutableSet<String> excludes; // No warning 176 * public final BigDecimal value; // Warning here, type BigDecimal isn't specified as immutable 177 * 178 * public ImmutableClass(Collection<String> includes, Collection<String> excludes, 179 * BigDecimal value) 180 * { 181 * this.includes = ImmutableSet.copyOf(includes); 182 * this.excludes = ImmutableSet.copyOf(excludes); 183 * this.value = value; 184 * this.notes = notes; 185 * } 186 * } 187 * } 188 * </pre> 189 * 190 * <p> 191 * To configure the Check passing fields annotated with 192 * </p> 193 * <pre>@com.annotation.CustomAnnotation</pre>: 194 195 * <p> 196 * <module name="VisibilityModifier"> 197 * <property name="ignoreAnnotationCanonicalNames" value=" 198 * com.annotation.CustomAnnotation"/> 199 * </module> 200 * </p> 201 * 202 * <pre> 203 * {@code @com.annotation.CustomAnnotation 204 * String customAnnotated; // No warning 205 * } 206 * {@code @CustomAnnotation 207 * String shortCustomAnnotated; // No warning 208 * } 209 * </pre> 210 * 211 * <p> 212 * To configure the Check passing fields annotated with short annotation name 213 * </p> 214 * <pre>@CustomAnnotation</pre>: 215 * 216 * <p> 217 * <module name="VisibilityModifier"> 218 * <property name="ignoreAnnotationCanonicalNames" 219 * value="CustomAnnotation"/> 220 * </module> 221 * </p> 222 * 223 * <pre> 224 * {@code @CustomAnnotation 225 * String customAnnotated; // No warning 226 * } 227 * {@code @com.annotation.CustomAnnotation 228 * String customAnnotated1; // No warning 229 * } 230 * {@code @mypackage.annotation.CustomAnnotation 231 * String customAnnotatedAnotherPackage; // another package but short name matches 232 * // so no violation 233 * } 234 * </pre> 235 * 236 * 237 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 238 */ 239public class VisibilityModifierCheck 240 extends AbstractCheck { 241 242 /** 243 * A key is pointing to the warning message text in "messages.properties" 244 * file. 245 */ 246 public static final String MSG_KEY = "variable.notPrivate"; 247 248 /** Default immutable types canonical names. */ 249 private static final List<String> DEFAULT_IMMUTABLE_TYPES = Collections.unmodifiableList( 250 Arrays.stream(new String[] { 251 "java.lang.String", 252 "java.lang.Integer", 253 "java.lang.Byte", 254 "java.lang.Character", 255 "java.lang.Short", 256 "java.lang.Boolean", 257 "java.lang.Long", 258 "java.lang.Double", 259 "java.lang.Float", 260 "java.lang.StackTraceElement", 261 "java.math.BigInteger", 262 "java.math.BigDecimal", 263 "java.io.File", 264 "java.util.Locale", 265 "java.util.UUID", 266 "java.net.URL", 267 "java.net.URI", 268 "java.net.Inet4Address", 269 "java.net.Inet6Address", 270 "java.net.InetSocketAddress", 271 }).collect(Collectors.toList())); 272 273 /** Default ignore annotations canonical names. */ 274 private static final List<String> DEFAULT_IGNORE_ANNOTATIONS = Collections.unmodifiableList( 275 Arrays.stream(new String[] { 276 "org.junit.Rule", 277 "org.junit.ClassRule", 278 "com.google.common.annotations.VisibleForTesting", 279 }).collect(Collectors.toList())); 280 281 /** Name for 'public' access modifier. */ 282 private static final String PUBLIC_ACCESS_MODIFIER = "public"; 283 284 /** Name for 'private' access modifier. */ 285 private static final String PRIVATE_ACCESS_MODIFIER = "private"; 286 287 /** Name for 'protected' access modifier. */ 288 private static final String PROTECTED_ACCESS_MODIFIER = "protected"; 289 290 /** Name for implicit 'package' access modifier. */ 291 private static final String PACKAGE_ACCESS_MODIFIER = "package"; 292 293 /** Name for 'static' keyword. */ 294 private static final String STATIC_KEYWORD = "static"; 295 296 /** Name for 'final' keyword. */ 297 private static final String FINAL_KEYWORD = "final"; 298 299 /** Contains explicit access modifiers. */ 300 private static final String[] EXPLICIT_MODS = { 301 PUBLIC_ACCESS_MODIFIER, 302 PRIVATE_ACCESS_MODIFIER, 303 PROTECTED_ACCESS_MODIFIER, 304 }; 305 306 /** Regexp for public members that should be ignored. Note: 307 * Earlier versions of checkstyle used ^f[A-Z][a-zA-Z0-9]*$ as the 308 * default to allow CMP for EJB 1.1 with the default settings. 309 * With EJB 2.0 it is not longer necessary to have public access 310 * for persistent fields. 311 */ 312 private Pattern publicMemberPattern = Pattern.compile("^serialVersionUID$"); 313 314 /** List of ignore annotations short names. */ 315 private final List<String> ignoreAnnotationShortNames = 316 getClassShortNames(DEFAULT_IGNORE_ANNOTATIONS); 317 318 /** List of immutable classes short names. */ 319 private final List<String> immutableClassShortNames = 320 getClassShortNames(DEFAULT_IMMUTABLE_TYPES); 321 322 /** List of ignore annotations canonical names. */ 323 private List<String> ignoreAnnotationCanonicalNames = 324 new ArrayList<>(DEFAULT_IGNORE_ANNOTATIONS); 325 326 /** Whether protected members are allowed. */ 327 private boolean protectedAllowed; 328 329 /** Whether package visible members are allowed. */ 330 private boolean packageAllowed; 331 332 /** Allows immutable fields of final classes to be declared as public. */ 333 private boolean allowPublicImmutableFields; 334 335 /** Allows final fields to be declared as public. */ 336 private boolean allowPublicFinalFields; 337 338 /** List of immutable classes canonical names. */ 339 private List<String> immutableClassCanonicalNames = new ArrayList<>(DEFAULT_IMMUTABLE_TYPES); 340 341 /** 342 * Set the list of ignore annotations. 343 * @param annotationNames array of ignore annotations canonical names. 344 */ 345 public void setIgnoreAnnotationCanonicalNames(String... annotationNames) { 346 ignoreAnnotationCanonicalNames = Arrays.asList(annotationNames); 347 } 348 349 /** 350 * Set whether protected members are allowed. 351 * @param protectedAllowed whether protected members are allowed 352 */ 353 public void setProtectedAllowed(boolean protectedAllowed) { 354 this.protectedAllowed = protectedAllowed; 355 } 356 357 /** 358 * Set whether package visible members are allowed. 359 * @param packageAllowed whether package visible members are allowed 360 */ 361 public void setPackageAllowed(boolean packageAllowed) { 362 this.packageAllowed = packageAllowed; 363 } 364 365 /** 366 * Set the pattern for public members to ignore. 367 * @param pattern 368 * pattern for public members to ignore. 369 */ 370 public void setPublicMemberPattern(Pattern pattern) { 371 publicMemberPattern = pattern; 372 } 373 374 /** 375 * Sets whether public immutable fields are allowed. 376 * @param allow user's value. 377 */ 378 public void setAllowPublicImmutableFields(boolean allow) { 379 allowPublicImmutableFields = allow; 380 } 381 382 /** 383 * Sets whether public final fields are allowed. 384 * @param allow user's value. 385 */ 386 public void setAllowPublicFinalFields(boolean allow) { 387 allowPublicFinalFields = allow; 388 } 389 390 /** 391 * Set the list of immutable classes types names. 392 * @param classNames array of immutable types canonical names. 393 */ 394 public void setImmutableClassCanonicalNames(String... classNames) { 395 immutableClassCanonicalNames = Arrays.asList(classNames); 396 } 397 398 @Override 399 public int[] getDefaultTokens() { 400 return getAcceptableTokens(); 401 } 402 403 @Override 404 public int[] getAcceptableTokens() { 405 return new int[] { 406 TokenTypes.VARIABLE_DEF, 407 TokenTypes.IMPORT, 408 }; 409 } 410 411 @Override 412 public int[] getRequiredTokens() { 413 return getAcceptableTokens(); 414 } 415 416 @Override 417 public void beginTree(DetailAST rootAst) { 418 immutableClassShortNames.clear(); 419 final List<String> classShortNames = 420 getClassShortNames(immutableClassCanonicalNames); 421 immutableClassShortNames.addAll(classShortNames); 422 423 ignoreAnnotationShortNames.clear(); 424 final List<String> annotationShortNames = 425 getClassShortNames(ignoreAnnotationCanonicalNames); 426 ignoreAnnotationShortNames.addAll(annotationShortNames); 427 } 428 429 @Override 430 public void visitToken(DetailAST ast) { 431 switch (ast.getType()) { 432 case TokenTypes.VARIABLE_DEF: 433 if (!isAnonymousClassVariable(ast)) { 434 visitVariableDef(ast); 435 } 436 break; 437 case TokenTypes.IMPORT: 438 visitImport(ast); 439 break; 440 default: 441 final String exceptionMsg = "Unexpected token type: " + ast.getText(); 442 throw new IllegalArgumentException(exceptionMsg); 443 } 444 } 445 446 /** 447 * Checks if current variable definition is definition of an anonymous class. 448 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 449 * @return true if current variable definition is definition of an anonymous class. 450 */ 451 private static boolean isAnonymousClassVariable(DetailAST variableDef) { 452 return variableDef.getParent().getType() != TokenTypes.OBJBLOCK; 453 } 454 455 /** 456 * Checks access modifier of given variable. 457 * If it is not proper according to Check - puts violation on it. 458 * @param variableDef variable to check. 459 */ 460 private void visitVariableDef(DetailAST variableDef) { 461 final boolean inInterfaceOrAnnotationBlock = 462 ScopeUtils.isInInterfaceOrAnnotationBlock(variableDef); 463 464 if (!inInterfaceOrAnnotationBlock && !hasIgnoreAnnotation(variableDef)) { 465 final DetailAST varNameAST = variableDef.findFirstToken(TokenTypes.TYPE) 466 .getNextSibling(); 467 final String varName = varNameAST.getText(); 468 if (!hasProperAccessModifier(variableDef, varName)) { 469 log(varNameAST.getLineNo(), varNameAST.getColumnNo(), 470 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.contains(shortName)) { 503 immutableClassShortNames.remove(shortName); 504 } 505 if (!ignoreAnnotationCanonicalNames.contains(canonicalName) 506 && ignoreAnnotationShortNames.contains(shortName)) { 507 ignoreAnnotationShortNames.remove(shortName); 508 } 509 } 510 } 511 512 /** 513 * Checks if current import is star import. E.g.: 514 * <p> 515 * {@code 516 * import java.util.*; 517 * } 518 * </p> 519 * @param importAst {@link TokenTypes#IMPORT Import} 520 * @return true if it is star import 521 */ 522 private static boolean isStarImport(DetailAST importAst) { 523 boolean result = false; 524 DetailAST toVisit = importAst; 525 while (toVisit != null) { 526 toVisit = getNextSubTreeNode(toVisit, importAst); 527 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) { 528 result = true; 529 break; 530 } 531 } 532 return result; 533 } 534 535 /** 536 * Checks if current variable has proper access modifier according to Check's options. 537 * @param variableDef Variable definition node. 538 * @param variableName Variable's name. 539 * @return true if variable has proper access modifier. 540 */ 541 private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) { 542 boolean result = true; 543 544 final String variableScope = getVisibilityScope(variableDef); 545 546 if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) { 547 result = 548 isStaticFinalVariable(variableDef) 549 || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope) 550 || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope) 551 || isIgnoredPublicMember(variableName, variableScope) 552 || isAllowedPublicField(variableDef); 553 } 554 555 return result; 556 } 557 558 /** 559 * Checks whether variable has static final modifiers. 560 * @param variableDef Variable definition node. 561 * @return true of variable has static final modifiers. 562 */ 563 private static boolean isStaticFinalVariable(DetailAST variableDef) { 564 final Set<String> modifiers = getModifiers(variableDef); 565 return modifiers.contains(STATIC_KEYWORD) 566 && modifiers.contains(FINAL_KEYWORD); 567 } 568 569 /** 570 * Checks whether variable belongs to public members that should be ignored. 571 * @param variableName Variable's name. 572 * @param variableScope Variable's scope. 573 * @return true if variable belongs to public members that should be ignored. 574 */ 575 private boolean isIgnoredPublicMember(String variableName, String variableScope) { 576 return PUBLIC_ACCESS_MODIFIER.equals(variableScope) 577 && publicMemberPattern.matcher(variableName).find(); 578 } 579 580 /** 581 * Checks whether the variable satisfies the public field check. 582 * @param variableDef Variable definition node. 583 * @return true if allowed. 584 */ 585 private boolean isAllowedPublicField(DetailAST variableDef) { 586 return allowPublicFinalFields && isFinalField(variableDef) 587 || allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef); 588 } 589 590 /** 591 * Checks whether immutable field is defined in final class. 592 * @param variableDef Variable definition node. 593 * @return true if immutable field is defined in final class. 594 */ 595 private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) { 596 final DetailAST classDef = variableDef.getParent().getParent(); 597 final Set<String> classModifiers = getModifiers(classDef); 598 return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF) 599 && isImmutableField(variableDef); 600 } 601 602 /** 603 * Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST. 604 * @param defAST AST for a variable or class definition. 605 * @return the set of modifier Strings for defAST. 606 */ 607 private static Set<String> getModifiers(DetailAST defAST) { 608 final AST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS); 609 final Set<String> modifiersSet = new HashSet<>(); 610 if (modifiersAST != null) { 611 AST modifier = modifiersAST.getFirstChild(); 612 while (modifier != null) { 613 modifiersSet.add(modifier.getText()); 614 modifier = modifier.getNextSibling(); 615 } 616 } 617 return modifiersSet; 618 } 619 620 /** 621 * Returns the visibility scope for the variable. 622 * @param variableDef Variable definition node. 623 * @return one of "public", "private", "protected", "package" 624 */ 625 private static String getVisibilityScope(DetailAST variableDef) { 626 final Set<String> modifiers = getModifiers(variableDef); 627 String accessModifier = PACKAGE_ACCESS_MODIFIER; 628 for (final String modifier : EXPLICIT_MODS) { 629 if (modifiers.contains(modifier)) { 630 accessModifier = modifier; 631 break; 632 } 633 } 634 return accessModifier; 635 } 636 637 /** 638 * Checks if current field is immutable: 639 * has final modifier and either a primitive type or instance of class 640 * known to be immutable (such as String, ImmutableCollection from Guava and etc). 641 * Classes known to be immutable are listed in 642 * {@link VisibilityModifierCheck#immutableClassCanonicalNames} 643 * @param variableDef Field in consideration. 644 * @return true if field is immutable. 645 */ 646 private boolean isImmutableField(DetailAST variableDef) { 647 boolean result = false; 648 if (isFinalField(variableDef)) { 649 final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE); 650 final boolean isCanonicalName = isCanonicalName(type); 651 final String typeName = getTypeName(type, isCanonicalName); 652 final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName); 653 if (typeArgs == null) { 654 result = !isCanonicalName && isPrimitive(type) 655 || immutableClassShortNames.contains(typeName) 656 || isCanonicalName && immutableClassCanonicalNames.contains(typeName); 657 } 658 else { 659 final List<String> argsClassNames = getTypeArgsClassNames(typeArgs); 660 result = (immutableClassShortNames.contains(typeName) 661 || isCanonicalName && immutableClassCanonicalNames.contains(typeName)) 662 && areImmutableTypeArguments(argsClassNames); 663 } 664 } 665 return result; 666 } 667 668 /** 669 * Checks whether type definition is in canonical form. 670 * @param type type definition token. 671 * @return true if type definition is in canonical form. 672 */ 673 private static boolean isCanonicalName(DetailAST type) { 674 return type.getFirstChild().getType() == TokenTypes.DOT; 675 } 676 677 /** 678 * Returns generic type arguments token. 679 * @param type type token. 680 * @param isCanonicalName whether type name is in canonical form. 681 * @return generic type arguments token. 682 */ 683 private static DetailAST getGenericTypeArgs(DetailAST type, boolean isCanonicalName) { 684 final DetailAST typeArgs; 685 if (isCanonicalName) { 686 // if type class name is in canonical form, abstract tree has specific structure 687 typeArgs = type.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS); 688 } 689 else { 690 typeArgs = type.findFirstToken(TokenTypes.TYPE_ARGUMENTS); 691 } 692 return typeArgs; 693 } 694 695 /** 696 * Returns a list of type parameters class names. 697 * @param typeArgs type arguments token. 698 * @return a list of type parameters class names. 699 */ 700 private static List<String> getTypeArgsClassNames(DetailAST typeArgs) { 701 final List<String> typeClassNames = new ArrayList<>(); 702 DetailAST type = typeArgs.findFirstToken(TokenTypes.TYPE_ARGUMENT); 703 boolean isCanonicalName = isCanonicalName(type); 704 String typeName = getTypeName(type, isCanonicalName); 705 typeClassNames.add(typeName); 706 DetailAST sibling = type.getNextSibling(); 707 while (sibling.getType() == TokenTypes.COMMA) { 708 type = sibling.getNextSibling(); 709 isCanonicalName = isCanonicalName(type); 710 typeName = getTypeName(type, isCanonicalName); 711 typeClassNames.add(typeName); 712 sibling = type.getNextSibling(); 713 } 714 return typeClassNames; 715 } 716 717 /** 718 * Checks whether all of generic type arguments are immutable. 719 * If at least one argument is mutable, we assume that the whole list of type arguments 720 * is mutable. 721 * @param typeArgsClassNames type arguments class names. 722 * @return true if all of generic type arguments are immutable. 723 */ 724 private boolean areImmutableTypeArguments(List<String> typeArgsClassNames) { 725 return !typeArgsClassNames.stream().filter( 726 typeName -> { 727 return !immutableClassShortNames.contains(typeName) 728 && !immutableClassCanonicalNames.contains(typeName); 729 }).findFirst().isPresent(); 730 } 731 732 /** 733 * Checks whether current field is final. 734 * @param variableDef field in consideration. 735 * @return true if current field is final. 736 */ 737 private static boolean isFinalField(DetailAST variableDef) { 738 final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS); 739 return modifiers.branchContains(TokenTypes.FINAL); 740 } 741 742 /** 743 * Gets the name of type from given ast {@link TokenTypes#TYPE TYPE} node. 744 * If type is specified via its canonical name - canonical name will be returned, 745 * else - short type's name. 746 * @param type {@link TokenTypes#TYPE TYPE} node. 747 * @param isCanonicalName is given name canonical. 748 * @return String representation of given type's name. 749 */ 750 private static String getTypeName(DetailAST type, boolean isCanonicalName) { 751 final String typeName; 752 if (isCanonicalName) { 753 typeName = getCanonicalName(type); 754 } 755 else { 756 typeName = type.getFirstChild().getText(); 757 } 758 return typeName; 759 } 760 761 /** 762 * Checks if current type is primitive type (int, short, float, boolean, double, etc.). 763 * As primitive types have special tokens for each one, such as: 764 * LITERAL_INT, LITERAL_BOOLEAN, etc. 765 * So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a 766 * primitive type. 767 * @param type Ast {@link TokenTypes#TYPE TYPE} node. 768 * @return true if current type is primitive type. 769 */ 770 private static boolean isPrimitive(DetailAST type) { 771 return type.getFirstChild().getType() != TokenTypes.IDENT; 772 } 773 774 /** 775 * Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node. 776 * @param type DetailAST {@link TokenTypes#TYPE TYPE} node. 777 * @return canonical type's name 778 */ 779 private static String getCanonicalName(DetailAST type) { 780 final StringBuilder canonicalNameBuilder = new StringBuilder(); 781 DetailAST toVisit = type.getFirstChild(); 782 while (toVisit != null) { 783 toVisit = getNextSubTreeNode(toVisit, type); 784 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) { 785 canonicalNameBuilder.append(toVisit.getText()); 786 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type); 787 if (nextSubTreeNode != null) { 788 if (nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) { 789 break; 790 } 791 canonicalNameBuilder.append('.'); 792 } 793 } 794 } 795 return canonicalNameBuilder.toString(); 796 } 797 798 /** 799 * Gets the next node of a syntactical tree (child of a current node or 800 * sibling of a current node, or sibling of a parent of a current node). 801 * @param currentNodeAst Current node in considering 802 * @param subTreeRootAst SubTree root 803 * @return Current node after bypassing, if current node reached the root of a subtree 804 * method returns null 805 */ 806 private static DetailAST 807 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) { 808 DetailAST currentNode = currentNodeAst; 809 DetailAST toVisitAst = currentNode.getFirstChild(); 810 while (toVisitAst == null) { 811 toVisitAst = currentNode.getNextSibling(); 812 if (toVisitAst == null) { 813 if (currentNode.getParent().equals(subTreeRootAst) 814 && currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) { 815 break; 816 } 817 currentNode = currentNode.getParent(); 818 } 819 } 820 return toVisitAst; 821 } 822 823 /** 824 * Gets the list with short names classes. 825 * These names are taken from array of classes canonical names. 826 * @param canonicalClassNames canonical class names. 827 * @return the list of short names of classes. 828 */ 829 private static List<String> getClassShortNames(List<String> canonicalClassNames) { 830 final List<String> shortNames = new ArrayList<>(); 831 for (String canonicalClassName : canonicalClassNames) { 832 final String shortClassName = canonicalClassName 833 .substring(canonicalClassName.lastIndexOf('.') + 1, 834 canonicalClassName.length()); 835 shortNames.add(shortClassName); 836 } 837 return shortNames; 838 } 839 840 /** 841 * Gets the short class name from given canonical name. 842 * @param canonicalClassName canonical class name. 843 * @return short name of class. 844 */ 845 private static String getClassShortName(String canonicalClassName) { 846 return canonicalClassName 847 .substring(canonicalClassName.lastIndexOf('.') + 1, 848 canonicalClassName.length()); 849 } 850 851 /** 852 * Checks whether the AST is annotated with 853 * an annotation containing the passed in regular 854 * expression and return the AST representing that 855 * annotation. 856 * 857 * <p> 858 * This method will not look for imports or package 859 * statements to detect the passed in annotation. 860 * </p> 861 * 862 * <p> 863 * To check if an AST contains a passed in annotation 864 * taking into account fully-qualified names 865 * (ex: java.lang.Override, Override) 866 * this method will need to be called twice. Once for each 867 * name given. 868 * </p> 869 * 870 * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}. 871 * @return the AST representing the first such annotation or null if 872 * no such annotation was found 873 */ 874 private DetailAST findMatchingAnnotation(DetailAST variableDef) { 875 DetailAST matchingAnnotation = null; 876 877 final DetailAST holder = AnnotationUtility.getAnnotationHolder(variableDef); 878 879 for (DetailAST child = holder.getFirstChild(); 880 child != null; child = child.getNextSibling()) { 881 if (child.getType() == TokenTypes.ANNOTATION) { 882 final DetailAST ast = child.getFirstChild(); 883 final String name = 884 FullIdent.createFullIdent(ast.getNextSibling()).getText(); 885 if (ignoreAnnotationCanonicalNames.contains(name) 886 || ignoreAnnotationShortNames.contains(name)) { 887 matchingAnnotation = child; 888 break; 889 } 890 } 891 } 892 893 return matchingAnnotation; 894 } 895}