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