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