001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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; 030 031import com.google.common.collect.ImmutableList; 032import com.puppycrawl.tools.checkstyle.api.Check; 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.CommonUtils; 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>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>allowPublicImmutableFields</b> - which allows immutable fields be 075 * declared as public if defined in final class. Default value is <b>true</b> 076 * </p> 077 * <p> 078 * Field is known to be immutable if: 079 * </p> 080 * <ul> 081 * <li>It's declared as final</li> 082 * <li>Has either a primitive type or instance of class user defined to be immutable 083 * (such as String, ImmutableCollection from Guava and etc)</li> 084 * </ul> 085 * <p> 086 * Classes known to be immutable are listed in <b>immutableClassCanonicalNames</b> by their 087 * <b>canonical</b> names. List by default: 088 * </p> 089 * <ul> 090 * <li>java.lang.String</li> 091 * <li>java.lang.Integer</li> 092 * <li>java.lang.Byte</li> 093 * <li>java.lang.Character</li> 094 * <li>java.lang.Short</li> 095 * <li>java.lang.Boolean</li> 096 * <li>java.lang.Long</li> 097 * <li>java.lang.Double</li> 098 * <li>java.lang.Float</li> 099 * <li>java.lang.StackTraceElement</li> 100 * <li>java.lang.BigInteger</li> 101 * <li>java.lang.BigDecimal</li> 102 * <li>java.io.File</li> 103 * <li>java.util.Locale</li> 104 * <li>java.util.UUID</li> 105 * <li>java.net.URL</li> 106 * <li>java.net.URI</li> 107 * <li>java.net.Inet4Address</li> 108 * <li>java.net.Inet6Address</li> 109 * <li>java.net.InetSocketAddress</li> 110 * </ul> 111 * <p> 112 * User can override this list via adding <b>canonical</b> class names to 113 * <b>immutableClassCanonicalNames</b>, if user will provide short class name all 114 * that type will match to any named the same type without consideration of package. 115 * </p> 116 * <p> 117 * <b>Rationale</b>: Forcing all fields of class to have private modified by default is good 118 * in most cases, but in some cases it drawbacks in too much boilerplate get/set code. 119 * One of such cases are immutable classes. 120 * </p> 121 * <p> 122 * <b>Restriction</b>: Check doesn't check if class is immutable, there's no checking 123 * if accessory methods are missing and all fields are immutable, we only check 124 * <b>if current field is immutable by matching a name to user defined list of immutable classes 125 * and defined in final class</b> 126 * </p> 127 * <p> 128 * Star imports are out of scope of this Check. So if one of type imported via <b>star import</b> 129 * collides with user specified one by its short name - there won't be Check's violation. 130 * </p> 131 * Examples: 132 * <p> 133 * Default Check's configuration will pass the code below: 134 * </p> 135 * 136 * <pre> 137 * {@code 138 * public final class ImmutableClass 139 * { 140 * public final int intValue; // No warning 141 * public final java.lang.String notes; // No warning 142 * public final BigDecimal value; // No warning 143 * 144 * public ImmutableClass(int intValue, BigDecimal value, String notes) 145 * { 146 * this.includes = ImmutableSet.copyOf(includes); 147 * this.excludes = ImmutableSet.copyOf(excludes); 148 * this.value = value; 149 * this.notes = notes; 150 * } 151 * } 152 * } 153 * </pre> 154 * 155 * <p> 156 * To configure the Check passing fields of type com.google.common.collect.ImmutableSet and 157 * java.util.List: 158 * </p> 159 * <p> 160 * <module name="VisibilityModifier"> 161 * <property name="immutableClassCanonicalNames" value="java.util.List, 162 * com.google.common.collect.ImmutableSet"/> 163 * </module> 164 * </p> 165 * 166 * <pre> 167 * {@code 168 * public final class ImmutableClass 169 * { 170 * public final ImmutableSet<String> includes; // No warning 171 * public final ImmutableSet<String> excludes; // No warning 172 * public final BigDecimal value; // Warning here, type BigDecimal isn't specified as immutable 173 * 174 * public ImmutableClass(Collection<String> includes, Collection<String> excludes, 175 * BigDecimal value) 176 * { 177 * this.includes = ImmutableSet.copyOf(includes); 178 * this.excludes = ImmutableSet.copyOf(excludes); 179 * this.value = value; 180 * this.notes = notes; 181 * } 182 * } 183 * } 184 * </pre> 185 * 186 * <p> 187 * To configure the Check passing fields annotated with 188 * </p> 189 * <pre>@com.annotation.CustomAnnotation</pre>: 190 191 * <p> 192 * <module name="VisibilityModifier"> 193 * <property name="ignoreAnnotationCanonicalNames" value=" 194 * com.annotation.CustomAnnotation"/> 195 * </module> 196 * </p> 197 * 198 * <pre> 199 * {@code @com.annotation.CustomAnnotation 200 * String customAnnotated; // No warning 201 * } 202 * {@code @CustomAnnotation 203 * String shortCustomAnnotated; // No warning 204 * } 205 * </pre> 206 * 207 * <p> 208 * To configure the Check passing fields annotated with short annotation name 209 * </p> 210 * <pre>@CustomAnnotation</pre>: 211 * 212 * <p> 213 * <module name="VisibilityModifier"> 214 * <property name="ignoreAnnotationCanonicalNames" 215 * value="CustomAnnotation"/> 216 * </module> 217 * </p> 218 * 219 * <pre> 220 * {@code @CustomAnnotation 221 * String customAnnotated; // No warning 222 * } 223 * {@code @com.annotation.CustomAnnotation 224 * String customAnnotated1; // No warning 225 * } 226 * {@code @mypackage.annotation.CustomAnnotation 227 * String customAnnotatedAnotherPackage; // another package but short name matches 228 * // so no violation 229 * } 230 * </pre> 231 * 232 * 233 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 234 */ 235public class VisibilityModifierCheck 236 extends Check { 237 238 /** 239 * A key is pointing to the warning message text in "messages.properties" 240 * file. 241 */ 242 public static final String MSG_KEY = "variable.notPrivate"; 243 244 /** Default immutable types canonical names. */ 245 private static final List<String> DEFAULT_IMMUTABLE_TYPES = ImmutableList.of( 246 "java.lang.String", 247 "java.lang.Integer", 248 "java.lang.Byte", 249 "java.lang.Character", 250 "java.lang.Short", 251 "java.lang.Boolean", 252 "java.lang.Long", 253 "java.lang.Double", 254 "java.lang.Float", 255 "java.lang.StackTraceElement", 256 "java.math.BigInteger", 257 "java.math.BigDecimal", 258 "java.io.File", 259 "java.util.Locale", 260 "java.util.UUID", 261 "java.net.URL", 262 "java.net.URI", 263 "java.net.Inet4Address", 264 "java.net.Inet6Address", 265 "java.net.InetSocketAddress" 266 ); 267 268 /** Default ignore annotations canonical names. */ 269 private static final List<String> DEFAULT_IGNORE_ANNOTATIONS = ImmutableList.of( 270 "org.junit.Rule", 271 "com.google.common.annotations.VisibleForTesting" 272 ); 273 274 /** Name for 'public' access modifier. */ 275 private static final String PUBLIC_ACCESS_MODIFIER = "public"; 276 277 /** Name for 'private' access modifier. */ 278 private static final String PRIVATE_ACCESS_MODIFIER = "private"; 279 280 /** Name for 'protected' access modifier. */ 281 private static final String PROTECTED_ACCESS_MODIFIER = "protected"; 282 283 /** Name for implicit 'package' access modifier. */ 284 private static final String PACKAGE_ACCESS_MODIFIER = "package"; 285 286 /** Name for 'static' keyword. */ 287 private static final String STATIC_KEYWORD = "static"; 288 289 /** Name for 'final' keyword. */ 290 private static final String FINAL_KEYWORD = "final"; 291 292 /** Contains explicit access modifiers. */ 293 private static final String[] EXPLICIT_MODS = { 294 PUBLIC_ACCESS_MODIFIER, 295 PRIVATE_ACCESS_MODIFIER, 296 PROTECTED_ACCESS_MODIFIER, 297 }; 298 299 /** Whether protected members are allowed. */ 300 private boolean protectedAllowed; 301 302 /** Whether package visible members are allowed. */ 303 private boolean packageAllowed; 304 305 /** 306 * Pattern 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 String publicMemberFormat = "^serialVersionUID$"; 313 314 /** Regexp for public members that should be ignored. */ 315 private Pattern publicMemberPattern = Pattern.compile(publicMemberFormat); 316 317 /** List of ignore annotations canonical names. */ 318 private List<String> ignoreAnnotationCanonicalNames = 319 new ArrayList<>(DEFAULT_IGNORE_ANNOTATIONS); 320 321 /** List of ignore annotations short names. */ 322 private final List<String> ignoreAnnotationShortNames = 323 getClassShortNames(DEFAULT_IGNORE_ANNOTATIONS); 324 325 /** Allows immutable fields to be declared as public. */ 326 private boolean allowPublicImmutableFields = true; 327 328 /** List of immutable classes canonical names. */ 329 private List<String> immutableClassCanonicalNames = new ArrayList<>(DEFAULT_IMMUTABLE_TYPES); 330 331 /** List of immutable classes short names. */ 332 private final List<String> immutableClassShortNames = 333 getClassShortNames(DEFAULT_IMMUTABLE_TYPES); 334 335 /** 336 * Set the list of ignore annotations. 337 * @param annotationNames array of ignore annotations canonical names. 338 */ 339 public void setIgnoreAnnotationCanonicalNames(String... annotationNames) { 340 ignoreAnnotationCanonicalNames = Arrays.asList(annotationNames); 341 } 342 343 /** 344 * Set whether protected members are allowed. 345 * @param protectedAllowed whether protected members are allowed 346 */ 347 public void setProtectedAllowed(boolean protectedAllowed) { 348 this.protectedAllowed = protectedAllowed; 349 } 350 351 /** 352 * Set whether package visible members are allowed. 353 * @param packageAllowed whether package visible members are allowed 354 */ 355 public void setPackageAllowed(boolean packageAllowed) { 356 this.packageAllowed = packageAllowed; 357 } 358 359 /** 360 * Set the pattern for public members to ignore. 361 * @param pattern 362 * pattern for public members to ignore. 363 * @throws org.apache.commons.beanutils.ConversionException 364 * if unable to create Pattern object 365 */ 366 public void setPublicMemberPattern(String pattern) { 367 publicMemberPattern = CommonUtils.createPattern(pattern); 368 publicMemberFormat = pattern; 369 } 370 371 /** 372 * Sets whether public immutable are allowed. 373 * @param allow user's value. 374 */ 375 public void setAllowPublicImmutableFields(boolean allow) { 376 allowPublicImmutableFields = allow; 377 } 378 379 /** 380 * Set the list of immutable classes types names. 381 * @param classNames array of immutable types canonical names. 382 */ 383 public void setImmutableClassCanonicalNames(String... classNames) { 384 immutableClassCanonicalNames = Arrays.asList(classNames); 385 } 386 387 @Override 388 public int[] getDefaultTokens() { 389 return getAcceptableTokens(); 390 } 391 392 @Override 393 public int[] getAcceptableTokens() { 394 return new int[] { 395 TokenTypes.VARIABLE_DEF, 396 TokenTypes.IMPORT, 397 }; 398 } 399 400 @Override 401 public int[] getRequiredTokens() { 402 return getAcceptableTokens(); 403 } 404 405 @Override 406 public void beginTree(DetailAST rootAst) { 407 immutableClassShortNames.clear(); 408 final List<String> classShortNames = 409 getClassShortNames(immutableClassCanonicalNames); 410 immutableClassShortNames.addAll(classShortNames); 411 412 ignoreAnnotationShortNames.clear(); 413 final List<String> annotationShortNames = 414 getClassShortNames(ignoreAnnotationCanonicalNames); 415 ignoreAnnotationShortNames.addAll(annotationShortNames); 416 } 417 418 @Override 419 public void visitToken(DetailAST ast) { 420 switch (ast.getType()) { 421 case TokenTypes.VARIABLE_DEF: 422 if (!isAnonymousClassVariable(ast)) { 423 visitVariableDef(ast); 424 } 425 break; 426 case TokenTypes.IMPORT: 427 visitImport(ast); 428 break; 429 default: 430 final String exceptionMsg = "Unexpected token type: " + ast.getText(); 431 throw new IllegalArgumentException(exceptionMsg); 432 } 433 } 434 435 /** 436 * Checks if current variable definition is definition of an anonymous class. 437 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 438 * @return true if current variable definition is definition of an anonymous class. 439 */ 440 private static boolean isAnonymousClassVariable(DetailAST variableDef) { 441 return variableDef.getParent().getType() != TokenTypes.OBJBLOCK; 442 } 443 444 /** 445 * Checks access modifier of given variable. 446 * If it is not proper according to Check - puts violation on it. 447 * @param variableDef variable to check. 448 */ 449 private void visitVariableDef(DetailAST variableDef) { 450 final boolean inInterfaceOrAnnotationBlock = 451 ScopeUtils.isInInterfaceOrAnnotationBlock(variableDef); 452 453 if (!inInterfaceOrAnnotationBlock && !hasIgnoreAnnotation(variableDef)) { 454 final DetailAST varNameAST = variableDef.findFirstToken(TokenTypes.TYPE) 455 .getNextSibling(); 456 final String varName = varNameAST.getText(); 457 if (!hasProperAccessModifier(variableDef, varName)) { 458 log(varNameAST.getLineNo(), varNameAST.getColumnNo(), 459 MSG_KEY, varName); 460 } 461 } 462 } 463 464 /** 465 * Checks if variable def has ignore annotation. 466 * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} 467 * @return true if variable def has ignore annotation. 468 */ 469 private boolean hasIgnoreAnnotation(DetailAST variableDef) { 470 final DetailAST firstIgnoreAnnotation = 471 findMatchingAnnotation(variableDef); 472 return firstIgnoreAnnotation != null; 473 } 474 475 /** 476 * Checks imported type. If type's canonical name was not specified in 477 * <b>immutableClassCanonicalNames</b>, but it's short name collides with one from 478 * <b>immutableClassShortNames</b> - removes it from the last one. 479 * @param importAst {@link TokenTypes#IMPORT Import} 480 */ 481 private void visitImport(DetailAST importAst) { 482 if (!isStarImport(importAst)) { 483 final DetailAST type = importAst.getFirstChild(); 484 final String canonicalName = getCanonicalName(type); 485 final String shortName = getClassShortName(canonicalName); 486 487 // If imported canonical class name is not specified as allowed immutable class, 488 // but its short name collides with one of specified class - removes the short name 489 // from list to avoid names collision 490 if (!immutableClassCanonicalNames.contains(canonicalName) 491 && immutableClassShortNames.contains(shortName)) { 492 immutableClassShortNames.remove(shortName); 493 } 494 if (!ignoreAnnotationCanonicalNames.contains(canonicalName) 495 && ignoreAnnotationShortNames.contains(shortName)) { 496 ignoreAnnotationShortNames.remove(shortName); 497 } 498 } 499 } 500 501 /** 502 * Checks if current import is star import. E.g.: 503 * <p> 504 * {@code 505 * import java.util.*; 506 * } 507 * </p> 508 * @param importAst {@link TokenTypes#IMPORT Import} 509 * @return true if it is star import 510 */ 511 private static boolean isStarImport(DetailAST importAst) { 512 boolean result = false; 513 DetailAST toVisit = importAst; 514 while (toVisit != null) { 515 toVisit = getNextSubTreeNode(toVisit, importAst); 516 if (toVisit != null && toVisit.getType() == TokenTypes.STAR) { 517 result = true; 518 break; 519 } 520 } 521 return result; 522 } 523 524 /** 525 * Checks if current variable has proper access modifier according to Check's options. 526 * @param variableDef Variable definition node. 527 * @param variableName Variable's name. 528 * @return true if variable has proper access modifier. 529 */ 530 private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) { 531 boolean result = true; 532 533 final String variableScope = getVisibilityScope(variableDef); 534 535 if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) { 536 result = 537 isStaticFinalVariable(variableDef) 538 || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope) 539 || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope) 540 || isIgnoredPublicMember(variableName, variableScope) 541 || allowPublicImmutableFields 542 && isImmutableFieldDefinedInFinalClass(variableDef); 543 } 544 545 return result; 546 } 547 548 /** 549 * Checks whether variable has static final modifiers. 550 * @param variableDef Variable definition node. 551 * @return true of variable has static final modifiers. 552 */ 553 private static boolean isStaticFinalVariable(DetailAST variableDef) { 554 final Set<String> modifiers = getModifiers(variableDef); 555 return modifiers.contains(STATIC_KEYWORD) 556 && modifiers.contains(FINAL_KEYWORD); 557 } 558 559 /** 560 * Checks whether variable belongs to public members that should be ignored. 561 * @param variableName Variable's name. 562 * @param variableScope Variable's scope. 563 * @return true if variable belongs to public members that should be ignored. 564 */ 565 private boolean isIgnoredPublicMember(String variableName, String variableScope) { 566 return PUBLIC_ACCESS_MODIFIER.equals(variableScope) 567 && publicMemberPattern.matcher(variableName).find(); 568 } 569 570 /** 571 * Checks whether immutable field is defined in final class. 572 * @param variableDef Variable definition node. 573 * @return true if immutable field is defined in final class. 574 */ 575 private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) { 576 final DetailAST classDef = variableDef.getParent().getParent(); 577 final Set<String> classModifiers = getModifiers(classDef); 578 return classModifiers.contains(FINAL_KEYWORD) && isImmutableField(variableDef); 579 } 580 581 /** 582 * Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST. 583 * @param defAST AST for a variable or class definition. 584 * @return the set of modifier Strings for defAST. 585 */ 586 private static Set<String> getModifiers(DetailAST defAST) { 587 final AST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS); 588 final Set<String> modifiersSet = new HashSet<>(); 589 if (modifiersAST != null) { 590 AST modifier = modifiersAST.getFirstChild(); 591 while (modifier != null) { 592 modifiersSet.add(modifier.getText()); 593 modifier = modifier.getNextSibling(); 594 } 595 } 596 return modifiersSet; 597 598 } 599 600 /** 601 * Returns the visibility scope for the variable. 602 * @param variableDef Variable definition node. 603 * @return one of "public", "private", "protected", "package" 604 */ 605 private static String getVisibilityScope(DetailAST variableDef) { 606 final Set<String> modifiers = getModifiers(variableDef); 607 String accessModifier = PACKAGE_ACCESS_MODIFIER; 608 for (final String modifier : EXPLICIT_MODS) { 609 if (modifiers.contains(modifier)) { 610 accessModifier = modifier; 611 break; 612 } 613 } 614 return accessModifier; 615 } 616 617 /** 618 * Checks if current field is immutable: 619 * has final modifier and either a primitive type or instance of class 620 * known to be immutable (such as String, ImmutableCollection from Guava and etc). 621 * Classes known to be immutable are listed in 622 * {@link VisibilityModifierCheck#immutableClassCanonicalNames} 623 * @param variableDef Field in consideration. 624 * @return true if field is immutable. 625 */ 626 private boolean isImmutableField(DetailAST variableDef) { 627 boolean result = false; 628 629 final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS); 630 final boolean isFinal = modifiers.branchContains(TokenTypes.FINAL); 631 if (isFinal) { 632 final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE); 633 final boolean isCanonicalName = type.getFirstChild().getType() == TokenTypes.DOT; 634 final String typeName = getTypeName(type, isCanonicalName); 635 636 result = !isCanonicalName && isPrimitive(type) 637 || immutableClassShortNames.contains(typeName) 638 || isCanonicalName && immutableClassCanonicalNames.contains(typeName); 639 } 640 return result; 641 } 642 643 /** 644 * Gets the name of type from given ast {@link TokenTypes#TYPE TYPE} node. 645 * If type is specified via its canonical name - canonical name will be returned, 646 * else - short type's name. 647 * @param type {@link TokenTypes#TYPE TYPE} node. 648 * @param isCanonicalName is given name canonical. 649 * @return String representation of given type's name. 650 */ 651 private static String getTypeName(DetailAST type, boolean isCanonicalName) { 652 String typeName; 653 if (isCanonicalName) { 654 typeName = getCanonicalName(type); 655 } 656 else { 657 typeName = type.getFirstChild().getText(); 658 } 659 return typeName; 660 } 661 662 /** 663 * Checks if current type is primitive type (int, short, float, boolean, double, etc.). 664 * As primitive types have special tokens for each one, such as: 665 * LITERAL_INT, LITERAL_BOOLEAN, etc. 666 * So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a 667 * primitive type. 668 * @param type Ast {@link TokenTypes#TYPE TYPE} node. 669 * @return true if current type is primitive type. 670 */ 671 private static boolean isPrimitive(DetailAST type) { 672 return type.getFirstChild().getType() != TokenTypes.IDENT; 673 } 674 675 /** 676 * Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node. 677 * @param type DetailAST {@link TokenTypes#TYPE TYPE} node. 678 * @return canonical type's name 679 */ 680 private static String getCanonicalName(DetailAST type) { 681 final StringBuilder canonicalNameBuilder = new StringBuilder(); 682 DetailAST toVisit = type.getFirstChild(); 683 while (toVisit != null) { 684 toVisit = getNextSubTreeNode(toVisit, type); 685 if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) { 686 canonicalNameBuilder.append(toVisit.getText()); 687 final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, 688 type); 689 if (nextSubTreeNode != null) { 690 canonicalNameBuilder.append('.'); 691 } 692 } 693 } 694 return canonicalNameBuilder.toString(); 695 } 696 697 /** 698 * Gets the next node of a syntactical tree (child of a current node or 699 * sibling of a current node, or sibling of a parent of a current node). 700 * @param currentNodeAst Current node in considering 701 * @param subTreeRootAst SubTree root 702 * @return Current node after bypassing, if current node reached the root of a subtree 703 * method returns null 704 */ 705 private static DetailAST 706 getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) { 707 DetailAST currentNode = currentNodeAst; 708 DetailAST toVisitAst = currentNode.getFirstChild(); 709 while (toVisitAst == null) { 710 toVisitAst = currentNode.getNextSibling(); 711 if (toVisitAst == null) { 712 if (currentNode.getParent().equals(subTreeRootAst) 713 && currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) { 714 break; 715 } 716 currentNode = currentNode.getParent(); 717 } 718 } 719 return toVisitAst; 720 } 721 722 /** 723 * Gets the list with short names classes. 724 * These names are taken from array of classes canonical names. 725 * @param canonicalClassNames canonical class names. 726 * @return the list of short names of classes. 727 */ 728 private static List<String> getClassShortNames(List<String> canonicalClassNames) { 729 final List<String> shortNames = new ArrayList<>(); 730 for (String canonicalClassName : canonicalClassNames) { 731 final String shortClassName = canonicalClassName 732 .substring(canonicalClassName.lastIndexOf('.') + 1, 733 canonicalClassName.length()); 734 shortNames.add(shortClassName); 735 } 736 return shortNames; 737 } 738 739 /** 740 * Gets the short class name from given canonical name. 741 * @param canonicalClassName canonical class name. 742 * @return short name of class. 743 */ 744 private static String getClassShortName(String canonicalClassName) { 745 return canonicalClassName 746 .substring(canonicalClassName.lastIndexOf('.') + 1, 747 canonicalClassName.length()); 748 } 749 750 /** 751 * Checks whether the AST is annotated with 752 * an annotation containing the passed in regular 753 * expression and return the AST representing that 754 * annotation. 755 * 756 * <p> 757 * This method will not look for imports or package 758 * statements to detect the passed in annotation. 759 * </p> 760 * 761 * <p> 762 * To check if an AST contains a passed in annotation 763 * taking into account fully-qualified names 764 * (ex: java.lang.Override, Override) 765 * this method will need to be called twice. Once for each 766 * name given. 767 * </p> 768 * 769 * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}. 770 * @return the AST representing the first such annotation or null if 771 * no such annotation was found 772 */ 773 private DetailAST findMatchingAnnotation(DetailAST variableDef) { 774 DetailAST matchingAnnotation = null; 775 776 final DetailAST holder = AnnotationUtility.getAnnotationHolder(variableDef); 777 778 for (DetailAST child = holder.getFirstChild(); 779 child != null; child = child.getNextSibling()) { 780 if (child.getType() == TokenTypes.ANNOTATION) { 781 final DetailAST ast = child.getFirstChild(); 782 final String name = 783 FullIdent.createFullIdent(ast.getNextSibling()).getText(); 784 if (ignoreAnnotationCanonicalNames.contains(name) 785 || ignoreAnnotationShortNames.contains(name)) { 786 matchingAnnotation = child; 787 break; 788 } 789 } 790 } 791 792 return matchingAnnotation; 793 } 794}