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.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 */ 240@FileStatefulCheck 241public class VisibilityModifierCheck 242 extends AbstractCheck { 243 244 /** 245 * A key is pointing to the warning message text in "messages.properties" 246 * file. 247 */ 248 public static final String MSG_KEY = "variable.notPrivate"; 249 250 /** Default immutable types canonical names. */ 251 private static final List<String> DEFAULT_IMMUTABLE_TYPES = Collections.unmodifiableList( 252 Arrays.stream(new String[] { 253 "java.lang.String", 254 "java.lang.Integer", 255 "java.lang.Byte", 256 "java.lang.Character", 257 "java.lang.Short", 258 "java.lang.Boolean", 259 "java.lang.Long", 260 "java.lang.Double", 261 "java.lang.Float", 262 "java.lang.StackTraceElement", 263 "java.math.BigInteger", 264 "java.math.BigDecimal", 265 "java.io.File", 266 "java.util.Locale", 267 "java.util.UUID", 268 "java.net.URL", 269 "java.net.URI", 270 "java.net.Inet4Address", 271 "java.net.Inet6Address", 272 "java.net.InetSocketAddress", 273 }).collect(Collectors.toList())); 274 275 /** Default ignore annotations canonical names. */ 276 private static final List<String> DEFAULT_IGNORE_ANNOTATIONS = Collections.unmodifiableList( 277 Arrays.stream(new String[] { 278 "org.junit.Rule", 279 "org.junit.ClassRule", 280 "com.google.common.annotations.VisibleForTesting", 281 }).collect(Collectors.toList())); 282 283 /** Name for 'public' access modifier. */ 284 private static final String PUBLIC_ACCESS_MODIFIER = "public"; 285 286 /** Name for 'private' access modifier. */ 287 private static final String PRIVATE_ACCESS_MODIFIER = "private"; 288 289 /** Name for 'protected' access modifier. */ 290 private static final String PROTECTED_ACCESS_MODIFIER = "protected"; 291 292 /** Name for implicit 'package' access modifier. */ 293 private static final String PACKAGE_ACCESS_MODIFIER = "package"; 294 295 /** Name for 'static' keyword. */ 296 private static final String STATIC_KEYWORD = "static"; 297 298 /** Name for 'final' keyword. */ 299 private static final String FINAL_KEYWORD = "final"; 300 301 /** Contains explicit access modifiers. */ 302 private static final String[] EXPLICIT_MODS = { 303 PUBLIC_ACCESS_MODIFIER, 304 PRIVATE_ACCESS_MODIFIER, 305 PROTECTED_ACCESS_MODIFIER, 306 }; 307 308 /** Regexp for public members that should be ignored. Note: 309 * Earlier versions of checkstyle used ^f[A-Z][a-zA-Z0-9]*$ as the 310 * default to allow CMP for EJB 1.1 with the default settings. 311 * With EJB 2.0 it is not longer necessary to have public access 312 * for persistent fields. 313 */ 314 private Pattern publicMemberPattern = Pattern.compile("^serialVersionUID$"); 315 316 /** List of ignore annotations short names. */ 317 private final List<String> ignoreAnnotationShortNames = 318 getClassShortNames(DEFAULT_IGNORE_ANNOTATIONS); 319 320 /** List of immutable classes short names. */ 321 private final List<String> immutableClassShortNames = 322 getClassShortNames(DEFAULT_IMMUTABLE_TYPES); 323 324 /** List of ignore annotations canonical names. */ 325 private List<String> ignoreAnnotationCanonicalNames = 326 new ArrayList<>(DEFAULT_IGNORE_ANNOTATIONS); 327 328 /** Whether protected members are allowed. */ 329 private boolean protectedAllowed; 330 331 /** Whether package visible members are allowed. */ 332 private boolean packageAllowed; 333 334 /** Allows immutable fields of final classes to be declared as public. */ 335 private boolean allowPublicImmutableFields; 336 337 /** Allows final fields to be declared as public. */ 338 private boolean allowPublicFinalFields; 339 340 /** List of immutable classes canonical names. */ 341 private List<String> immutableClassCanonicalNames = new ArrayList<>(DEFAULT_IMMUTABLE_TYPES); 342 343 /** 344 * Set the list of ignore annotations. 345 * @param annotationNames array of ignore annotations canonical names. 346 */ 347 public void setIgnoreAnnotationCanonicalNames(String... annotationNames) { 348 ignoreAnnotationCanonicalNames = Arrays.asList(annotationNames); 349 } 350 351 /** 352 * Set whether protected members are allowed. 353 * @param protectedAllowed whether protected members are allowed 354 */ 355 public void setProtectedAllowed(boolean protectedAllowed) { 356 this.protectedAllowed = protectedAllowed; 357 } 358 359 /** 360 * Set whether package visible members are allowed. 361 * @param packageAllowed whether package visible members are allowed 362 */ 363 public void setPackageAllowed(boolean packageAllowed) { 364 this.packageAllowed = packageAllowed; 365 } 366 367 /** 368 * Set the pattern for public members to ignore. 369 * @param pattern 370 * pattern for public members to ignore. 371 */ 372 public void setPublicMemberPattern(Pattern pattern) { 373 publicMemberPattern = pattern; 374 } 375 376 /** 377 * Sets whether public immutable fields are allowed. 378 * @param allow user's value. 379 */ 380 public void setAllowPublicImmutableFields(boolean allow) { 381 allowPublicImmutableFields = allow; 382 } 383 384 /** 385 * Sets whether public final fields are allowed. 386 * @param allow user's value. 387 */ 388 public void setAllowPublicFinalFields(boolean allow) { 389 allowPublicFinalFields = allow; 390 } 391 392 /** 393 * Set the list of immutable classes types names. 394 * @param classNames array of immutable types canonical names. 395 */ 396 public void setImmutableClassCanonicalNames(String... classNames) { 397 immutableClassCanonicalNames = Arrays.asList(classNames); 398 } 399 400 @Override 401 public int[] getDefaultTokens() { 402 return getRequiredTokens(); 403 } 404 405 @Override 406 public int[] getAcceptableTokens() { 407 return getRequiredTokens(); 408 } 409 410 @Override 411 public int[] getRequiredTokens() { 412 return new int[] { 413 TokenTypes.VARIABLE_DEF, 414 TokenTypes.IMPORT, 415 }; 416 } 417 418 @Override 419 public void beginTree(DetailAST rootAst) { 420 immutableClassShortNames.clear(); 421 final List<String> classShortNames = 422 getClassShortNames(immutableClassCanonicalNames); 423 immutableClassShortNames.addAll(classShortNames); 424 425 ignoreAnnotationShortNames.clear(); 426 final List<String> annotationShortNames = 427 getClassShortNames(ignoreAnnotationCanonicalNames); 428 ignoreAnnotationShortNames.addAll(annotationShortNames); 429 } 430 431 @Override 432 public void visitToken(DetailAST ast) { 433 switch (ast.getType()) { 434 case TokenTypes.VARIABLE_DEF: 435 if (!isAnonymousClassVariable(ast)) { 436 visitVariableDef(ast); 437 } 438 break; 439 case TokenTypes.IMPORT: 440 visitImport(ast); 441 break; 442 default: 443 final String exceptionMsg = "Unexpected token type: " + ast.getText(); 444 throw new IllegalArgumentException(exceptionMsg); 445 } 446 } 447 448 /** 449 * Checks if current variable definition is definition of an anonymous class. 450 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 451 * @return true if current variable definition is definition of an anonymous class. 452 */ 453 private static boolean isAnonymousClassVariable(DetailAST variableDef) { 454 return variableDef.getParent().getType() != TokenTypes.OBJBLOCK; 455 } 456 457 /** 458 * Checks access modifier of given variable. 459 * If it is not proper according to Check - puts violation on it. 460 * @param variableDef variable to check. 461 */ 462 private void visitVariableDef(DetailAST variableDef) { 463 final boolean inInterfaceOrAnnotationBlock = 464 ScopeUtils.isInInterfaceOrAnnotationBlock(variableDef); 465 466 if (!inInterfaceOrAnnotationBlock && !hasIgnoreAnnotation(variableDef)) { 467 final DetailAST varNameAST = variableDef.findFirstToken(TokenTypes.TYPE) 468 .getNextSibling(); 469 final String varName = varNameAST.getText(); 470 if (!hasProperAccessModifier(variableDef, varName)) { 471 log(varNameAST.getLineNo(), varNameAST.getColumnNo(), 472 MSG_KEY, varName); 473 } 474 } 475 } 476 477 /** 478 * Checks if variable def has ignore annotation. 479 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 480 * @return true if variable def has ignore annotation. 481 */ 482 private boolean hasIgnoreAnnotation(DetailAST variableDef) { 483 final DetailAST firstIgnoreAnnotation = 484 findMatchingAnnotation(variableDef); 485 return firstIgnoreAnnotation != null; 486 } 487 488 /** 489 * Checks imported type. If type's canonical name was not specified in 490 * <b>immutableClassCanonicalNames</b>, but it's short name collides with one from 491 * <b>immutableClassShortNames</b> - removes it from the last one. 492 * @param importAst {@link TokenTypes#IMPORT Import} 493 */ 494 private void visitImport(DetailAST importAst) { 495 if (!isStarImport(importAst)) { 496 final DetailAST type = importAst.getFirstChild(); 497 final String canonicalName = getCanonicalName(type); 498 final String shortName = getClassShortName(canonicalName); 499 500 // If imported canonical class name is not specified as allowed immutable class, 501 // but its short name collides with one of specified class - removes the short name 502 // from list to avoid names collision 503 if (!immutableClassCanonicalNames.contains(canonicalName) 504 && immutableClassShortNames.contains(shortName)) { 505 immutableClassShortNames.remove(shortName); 506 } 507 if (!ignoreAnnotationCanonicalNames.contains(canonicalName) 508 && ignoreAnnotationShortNames.contains(shortName)) { 509 ignoreAnnotationShortNames.remove(shortName); 510 } 511 } 512 } 513 514 /** 515 * Checks if current import is star import. E.g.: 516 * <p> 517 * {@code 518 * import java.util.*; 519 * } 520 * </p> 521 * @param importAst {@link TokenTypes#IMPORT Import} 522 * @return true if it is star import 523 */ 524 private static boolean isStarImport(DetailAST importAst) { 525 boolean result = false; 526 DetailAST toVisit = importAst; 527 while (toVisit != null) { 528 toVisit = getNextSubTreeNode(toVisit, importAst); 529 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) { 530 result = true; 531 break; 532 } 533 } 534 return result; 535 } 536 537 /** 538 * Checks if current variable has proper access modifier according to Check's options. 539 * @param variableDef Variable definition node. 540 * @param variableName Variable's name. 541 * @return true if variable has proper access modifier. 542 */ 543 private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) { 544 boolean result = true; 545 546 final String variableScope = getVisibilityScope(variableDef); 547 548 if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) { 549 result = 550 isStaticFinalVariable(variableDef) 551 || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope) 552 || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope) 553 || isIgnoredPublicMember(variableName, variableScope) 554 || isAllowedPublicField(variableDef); 555 } 556 557 return result; 558 } 559 560 /** 561 * Checks whether variable has static final modifiers. 562 * @param variableDef Variable definition node. 563 * @return true of variable has static final modifiers. 564 */ 565 private static boolean isStaticFinalVariable(DetailAST variableDef) { 566 final Set<String> modifiers = getModifiers(variableDef); 567 return modifiers.contains(STATIC_KEYWORD) 568 && modifiers.contains(FINAL_KEYWORD); 569 } 570 571 /** 572 * Checks whether variable belongs to public members that should be ignored. 573 * @param variableName Variable's name. 574 * @param variableScope Variable's scope. 575 * @return true if variable belongs to public members that should be ignored. 576 */ 577 private boolean isIgnoredPublicMember(String variableName, String variableScope) { 578 return PUBLIC_ACCESS_MODIFIER.equals(variableScope) 579 && publicMemberPattern.matcher(variableName).find(); 580 } 581 582 /** 583 * Checks whether the variable satisfies the public field check. 584 * @param variableDef Variable definition node. 585 * @return true if allowed. 586 */ 587 private boolean isAllowedPublicField(DetailAST variableDef) { 588 return allowPublicFinalFields && isFinalField(variableDef) 589 || allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef); 590 } 591 592 /** 593 * Checks whether immutable field is defined in final class. 594 * @param variableDef Variable definition node. 595 * @return true if immutable field is defined in final class. 596 */ 597 private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) { 598 final DetailAST classDef = variableDef.getParent().getParent(); 599 final Set<String> classModifiers = getModifiers(classDef); 600 return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF) 601 && isImmutableField(variableDef); 602 } 603 604 /** 605 * Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST. 606 * @param defAST AST for a variable or class definition. 607 * @return the set of modifier Strings for defAST. 608 */ 609 private static Set<String> getModifiers(DetailAST defAST) { 610 final AST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS); 611 final Set<String> modifiersSet = new HashSet<>(); 612 if (modifiersAST != null) { 613 AST modifier = modifiersAST.getFirstChild(); 614 while (modifier != null) { 615 modifiersSet.add(modifier.getText()); 616 modifier = modifier.getNextSibling(); 617 } 618 } 619 return modifiersSet; 620 } 621 622 /** 623 * Returns the visibility scope for the variable. 624 * @param variableDef Variable definition node. 625 * @return one of "public", "private", "protected", "package" 626 */ 627 private static String getVisibilityScope(DetailAST variableDef) { 628 final Set<String> modifiers = getModifiers(variableDef); 629 String accessModifier = PACKAGE_ACCESS_MODIFIER; 630 for (final String modifier : EXPLICIT_MODS) { 631 if (modifiers.contains(modifier)) { 632 accessModifier = modifier; 633 break; 634 } 635 } 636 return accessModifier; 637 } 638 639 /** 640 * Checks if current field is immutable: 641 * has final modifier and either a primitive type or instance of class 642 * known to be immutable (such as String, ImmutableCollection from Guava and etc). 643 * Classes known to be immutable are listed in 644 * {@link VisibilityModifierCheck#immutableClassCanonicalNames} 645 * @param variableDef Field in consideration. 646 * @return true if field is immutable. 647 */ 648 private boolean isImmutableField(DetailAST variableDef) { 649 boolean result = false; 650 if (isFinalField(variableDef)) { 651 final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE); 652 final boolean isCanonicalName = isCanonicalName(type); 653 final String typeName = getTypeName(type, isCanonicalName); 654 final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName); 655 if (typeArgs == null) { 656 result = !isCanonicalName && isPrimitive(type) 657 || immutableClassShortNames.contains(typeName) 658 || isCanonicalName && immutableClassCanonicalNames.contains(typeName); 659 } 660 else { 661 final List<String> argsClassNames = getTypeArgsClassNames(typeArgs); 662 result = (immutableClassShortNames.contains(typeName) 663 || isCanonicalName && immutableClassCanonicalNames.contains(typeName)) 664 && areImmutableTypeArguments(argsClassNames); 665 } 666 } 667 return result; 668 } 669 670 /** 671 * Checks whether type definition is in canonical form. 672 * @param type type definition token. 673 * @return true if type definition is in canonical form. 674 */ 675 private static boolean isCanonicalName(DetailAST type) { 676 return type.getFirstChild().getType() == TokenTypes.DOT; 677 } 678 679 /** 680 * Returns generic type arguments token. 681 * @param type type token. 682 * @param isCanonicalName whether type name is in canonical form. 683 * @return generic type arguments token. 684 */ 685 private static DetailAST getGenericTypeArgs(DetailAST type, boolean isCanonicalName) { 686 final DetailAST typeArgs; 687 if (isCanonicalName) { 688 // if type class name is in canonical form, abstract tree has specific structure 689 typeArgs = type.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS); 690 } 691 else { 692 typeArgs = type.findFirstToken(TokenTypes.TYPE_ARGUMENTS); 693 } 694 return typeArgs; 695 } 696 697 /** 698 * Returns a list of type parameters class names. 699 * @param typeArgs type arguments token. 700 * @return a list of type parameters class names. 701 */ 702 private static List<String> getTypeArgsClassNames(DetailAST typeArgs) { 703 final List<String> typeClassNames = new ArrayList<>(); 704 DetailAST type = typeArgs.findFirstToken(TokenTypes.TYPE_ARGUMENT); 705 boolean isCanonicalName = isCanonicalName(type); 706 String typeName = getTypeName(type, isCanonicalName); 707 typeClassNames.add(typeName); 708 DetailAST sibling = type.getNextSibling(); 709 while (sibling.getType() == TokenTypes.COMMA) { 710 type = sibling.getNextSibling(); 711 isCanonicalName = isCanonicalName(type); 712 typeName = getTypeName(type, isCanonicalName); 713 typeClassNames.add(typeName); 714 sibling = type.getNextSibling(); 715 } 716 return typeClassNames; 717 } 718 719 /** 720 * Checks whether all of generic type arguments are immutable. 721 * If at least one argument is mutable, we assume that the whole list of type arguments 722 * is mutable. 723 * @param typeArgsClassNames type arguments class names. 724 * @return true if all of generic type arguments are immutable. 725 */ 726 private boolean areImmutableTypeArguments(List<String> typeArgsClassNames) { 727 return typeArgsClassNames.stream().noneMatch( 728 typeName -> { 729 return !immutableClassShortNames.contains(typeName) 730 && !immutableClassCanonicalNames.contains(typeName); 731 }); 732 } 733 734 /** 735 * Checks whether current field is final. 736 * @param variableDef field in consideration. 737 * @return true if current field is final. 738 */ 739 private static boolean isFinalField(DetailAST variableDef) { 740 final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS); 741 return modifiers.findFirstToken(TokenTypes.FINAL) != null; 742 } 743 744 /** 745 * Gets the name of type from given ast {@link TokenTypes#TYPE TYPE} node. 746 * If type is specified via its canonical name - canonical name will be returned, 747 * else - short type's name. 748 * @param type {@link TokenTypes#TYPE TYPE} node. 749 * @param isCanonicalName is given name canonical. 750 * @return String representation of given type's name. 751 */ 752 private static String getTypeName(DetailAST type, boolean isCanonicalName) { 753 final String typeName; 754 if (isCanonicalName) { 755 typeName = getCanonicalName(type); 756 } 757 else { 758 typeName = type.getFirstChild().getText(); 759 } 760 return typeName; 761 } 762 763 /** 764 * Checks if current type is primitive type (int, short, float, boolean, double, etc.). 765 * As primitive types have special tokens for each one, such as: 766 * LITERAL_INT, LITERAL_BOOLEAN, etc. 767 * So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a 768 * primitive type. 769 * @param type Ast {@link TokenTypes#TYPE TYPE} node. 770 * @return true if current type is primitive type. 771 */ 772 private static boolean isPrimitive(DetailAST type) { 773 return type.getFirstChild().getType() != TokenTypes.IDENT; 774 } 775 776 /** 777 * Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node. 778 * @param type DetailAST {@link TokenTypes#TYPE TYPE} node. 779 * @return canonical type's name 780 */ 781 private static String getCanonicalName(DetailAST type) { 782 final StringBuilder canonicalNameBuilder = new StringBuilder(256); 783 DetailAST toVisit = type.getFirstChild(); 784 while (toVisit != null) { 785 toVisit = getNextSubTreeNode(toVisit, type); 786 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) { 787 canonicalNameBuilder.append(toVisit.getText()); 788 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type); 789 if (nextSubTreeNode != null) { 790 if (nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) { 791 break; 792 } 793 canonicalNameBuilder.append('.'); 794 } 795 } 796 } 797 return canonicalNameBuilder.toString(); 798 } 799 800 /** 801 * Gets the next node of a syntactical tree (child of a current node or 802 * sibling of a current node, or sibling of a parent of a current node). 803 * @param currentNodeAst Current node in considering 804 * @param subTreeRootAst SubTree root 805 * @return Current node after bypassing, if current node reached the root of a subtree 806 * method returns null 807 */ 808 private static DetailAST 809 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) { 810 DetailAST currentNode = currentNodeAst; 811 DetailAST toVisitAst = currentNode.getFirstChild(); 812 while (toVisitAst == null) { 813 toVisitAst = currentNode.getNextSibling(); 814 if (toVisitAst == null) { 815 if (currentNode.getParent().equals(subTreeRootAst) 816 && currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) { 817 break; 818 } 819 currentNode = currentNode.getParent(); 820 } 821 } 822 return toVisitAst; 823 } 824 825 /** 826 * Gets the list with short names classes. 827 * These names are taken from array of classes canonical names. 828 * @param canonicalClassNames canonical class names. 829 * @return the list of short names of classes. 830 */ 831 private static List<String> getClassShortNames(List<String> canonicalClassNames) { 832 final List<String> shortNames = new ArrayList<>(); 833 for (String canonicalClassName : canonicalClassNames) { 834 final String shortClassName = canonicalClassName 835 .substring(canonicalClassName.lastIndexOf('.') + 1, 836 canonicalClassName.length()); 837 shortNames.add(shortClassName); 838 } 839 return shortNames; 840 } 841 842 /** 843 * Gets the short class name from given canonical name. 844 * @param canonicalClassName canonical class name. 845 * @return short name of class. 846 */ 847 private static String getClassShortName(String canonicalClassName) { 848 return canonicalClassName 849 .substring(canonicalClassName.lastIndexOf('.') + 1, 850 canonicalClassName.length()); 851 } 852 853 /** 854 * Checks whether the AST is annotated with 855 * an annotation containing the passed in regular 856 * expression and return the AST representing that 857 * annotation. 858 * 859 * <p> 860 * This method will not look for imports or package 861 * statements to detect the passed in annotation. 862 * </p> 863 * 864 * <p> 865 * To check if an AST contains a passed in annotation 866 * taking into account fully-qualified names 867 * (ex: java.lang.Override, Override) 868 * this method will need to be called twice. Once for each 869 * name given. 870 * </p> 871 * 872 * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}. 873 * @return the AST representing the first such annotation or null if 874 * no such annotation was found 875 */ 876 private DetailAST findMatchingAnnotation(DetailAST variableDef) { 877 DetailAST matchingAnnotation = null; 878 879 final DetailAST holder = AnnotationUtility.getAnnotationHolder(variableDef); 880 881 for (DetailAST child = holder.getFirstChild(); 882 child != null; child = child.getNextSibling()) { 883 if (child.getType() == TokenTypes.ANNOTATION) { 884 final DetailAST ast = child.getFirstChild(); 885 final String name = 886 FullIdent.createFullIdent(ast.getNextSibling()).getText(); 887 if (ignoreAnnotationCanonicalNames.contains(name) 888 || ignoreAnnotationShortNames.contains(name)) { 889 matchingAnnotation = child; 890 break; 891 } 892 } 893 } 894 895 return matchingAnnotation; 896 } 897 898}