001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.coding; 021 022import java.util.HashSet; 023import java.util.Locale; 024import java.util.Objects; 025import java.util.Set; 026import java.util.regex.Pattern; 027 028import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 029import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.Scope; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 034import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 035import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 036 037/** 038 * <p> 039 * Checks that a local variable or a parameter does not shadow 040 * a field that is defined in the same class. 041 * </p> 042 * <p> 043 * It is possible to configure the check to ignore all property setter methods. 044 * </p> 045 * <p> 046 * A method is recognized as a setter if it is in the following form 047 * </p> 048 * <pre> 049 * ${returnType} set${Name}(${anyType} ${name}) { ... } 050 * </pre> 051 * <p> 052 * where ${anyType} is any primitive type, class or interface name; 053 * ${name} is name of the variable that is being set and ${Name} its 054 * capitalized form that appears in the method name. By default it is expected 055 * that setter returns void, i.e. ${returnType} is 'void'. For example 056 * </p> 057 * <pre> 058 * void setTime(long time) { ... } 059 * </pre> 060 * <p> 061 * Any other return types will not let method match a setter pattern. However, 062 * by setting <em>setterCanReturnItsClass</em> property to <em>true</em> 063 * definition of a setter is expanded, so that setter return type can also be 064 * a class in which setter is declared. For example 065 * </p> 066 * <pre> 067 * class PageBuilder { 068 * PageBuilder setName(String name) { ... } 069 * } 070 * </pre> 071 * <p> 072 * Such methods are known as chain-setters and a common when Builder-pattern 073 * is used. Property <em>setterCanReturnItsClass</em> has effect only if 074 * <em>ignoreSetter</em> is set to true. 075 * </p> 076 * <ul> 077 * <li> 078 * Property {@code ignoreFormat} - Define the RegExp for names of variables 079 * and parameters to ignore. 080 * Type is {@code java.util.regex.Pattern}. 081 * Default value is {@code null}. 082 * </li> 083 * <li> 084 * Property {@code ignoreConstructorParameter} - Control whether to ignore constructor parameters. 085 * Type is {@code boolean}. 086 * Default value is {@code false}. 087 * </li> 088 * <li> 089 * Property {@code ignoreSetter} - Allow to ignore the parameter of a property setter method. 090 * Type is {@code boolean}. 091 * Default value is {@code false}. 092 * </li> 093 * <li> 094 * Property {@code setterCanReturnItsClass} - Allow to expand the definition of a setter method 095 * to include methods that return the class' instance. 096 * Type is {@code boolean}. 097 * Default value is {@code false}. 098 * </li> 099 * <li> 100 * Property {@code ignoreAbstractMethods} - Control whether to ignore parameters 101 * of abstract methods. 102 * Type is {@code boolean}. 103 * Default value is {@code false}. 104 * </li> 105 * <li> 106 * Property {@code tokens} - tokens to check 107 * Type is {@code java.lang.String[]}. 108 * Validation type is {@code tokenSet}. 109 * Default value is: 110 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 111 * VARIABLE_DEF</a>, 112 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PARAMETER_DEF"> 113 * PARAMETER_DEF</a>, 114 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PATTERN_VARIABLE_DEF"> 115 * PATTERN_VARIABLE_DEF</a>, 116 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA"> 117 * LAMBDA</a>, 118 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_COMPONENT_DEF"> 119 * RECORD_COMPONENT_DEF</a>. 120 * </li> 121 * </ul> 122 * <p> 123 * To configure the check: 124 * </p> 125 * <pre> 126 * <module name="HiddenField"/> 127 * </pre> 128 * <pre> 129 * public class SomeClass { 130 * 131 * private String field; 132 * private String testField; 133 * 134 * public SomeClass(String testField) { // violation, 'testField' param hides 'testField' field 135 * } 136 * public void method(String param) { // OK 137 * String field = param; // violation, 'field' variable hides 'field' field 138 * } 139 * public void setTestField(String testField) { // violation, 'testField' param 140 * // hides 'testField' field 141 * this.field = field; 142 * } 143 * public SomeClass setField(String field) { // violation, 'field' param hides 'field' field 144 * this.field = field; 145 * } 146 * } 147 * </pre> 148 * 149 * <p> 150 * To configure the check so that it checks local variables but not parameters: 151 * </p> 152 * <pre> 153 * <module name="HiddenField"> 154 * <property name="tokens" value="VARIABLE_DEF"/> 155 * </module> 156 * </pre> 157 * <pre> 158 * public class SomeClass { 159 * 160 * private String field; 161 * private String testField; 162 * 163 * public SomeClass(String testField) { // OK, 'testField' param doesn't hide any field 164 * } 165 * public void method(String param) { // OK 166 * String field = param; // violation, 'field' variable hides 'field' field 167 * } 168 * public void setTestField(String testField) { // OK, 'testField' param doesn't hide any field 169 * this.field = field; 170 * } 171 * public SomeClass setField(String field) { // OK, 'field' param doesn't hide any field 172 * this.field = field; 173 * } 174 * } 175 * </pre> 176 * 177 * <p> 178 * To configure the check so that it ignores the variables and parameters named "test": 179 * </p> 180 * <pre> 181 * <module name="HiddenField"> 182 * <property name="ignoreFormat" value="^testField"/> 183 * </module> 184 * </pre> 185 * <pre> 186 * public class SomeClass { 187 * 188 * private String field; 189 * private String testField; 190 * 191 * public SomeClass(String testField) { // OK, because it match ignoreFormat 192 * } 193 * public void method(String param) { // OK 194 * String field = param; // violation, 'field' variable hides 'field' field 195 * } 196 * public void setTestField(String testField) { // OK, because it match ignoreFormat 197 * this.field = field; 198 * } 199 * public SomeClass setField(String field) { // violation, 'field' param hides 'field' field 200 * this.field = field; 201 * } 202 * } 203 * </pre> 204 * <p> 205 * To configure the check so that it ignores constructor parameters: 206 * </p> 207 * <pre> 208 * <module name="HiddenField"> 209 * <property name="ignoreConstructorParameter" value="true"/> 210 * </module> 211 * </pre> 212 * <pre> 213 * public class SomeClass { 214 * 215 * private String field; 216 * private String testField; 217 * 218 * public SomeClass(String testField) { // OK, 'testField' param doesn't hide any field 219 * } 220 * public void method(String param) { // OK 221 * String field = param; // violation, 'field' variable hides 'field' field 222 * } 223 * public void setTestField(String testField) { // violation, 'testField' variable 224 * // hides 'testField' field 225 * this.field = field; 226 * } 227 * public SomeClass setField(String field) { // violation, 'field' param hides 'field' field 228 * this.field = field; 229 * } 230 * } 231 * </pre> 232 * <p> 233 * To configure the check so that it ignores the parameter of setter methods: 234 * </p> 235 * <pre> 236 * <module name="HiddenField"> 237 * <property name="ignoreSetter" value="true"/> 238 * </module> 239 * </pre> 240 * <pre> 241 * public class SomeClass { 242 * 243 * private String field; 244 * private String testField; 245 * 246 * public SomeClass(String testField) { // violation, 'testField' param hides 'testField' field 247 * } 248 * public void method(String param) { // OK 249 * String field = param; // violation, 'field' variable hides 'field' field 250 * } 251 * public void setTestField(String testField) { // OK, 'testField' param doesn't hide any field 252 * this.field = field; 253 * } 254 * public SomeClass setField(String field) { // violation, 'field' param hides 'field' field 255 * this.field = field; 256 * } 257 * } 258 * </pre> 259 * <p> 260 * To configure the check so that it ignores the parameter of setter methods 261 * recognizing setter as returning either {@code void} or a class in which it is declared: 262 * </p> 263 * <pre> 264 * <module name="HiddenField"> 265 * <property name="ignoreSetter" value="true"/> 266 * <property name="setterCanReturnItsClass" value="true"/> 267 * </module> 268 * </pre> 269 * <pre> 270 * public class SomeClass { 271 * 272 * private String field; 273 * private String testField; 274 * 275 * public SomeClass(String testField) { // violation, 'testField' param hides 'testField' field 276 * } 277 * public void method(String param) { // OK 278 * String field = param; // violation, 'field' variable hides 'field' field 279 * } 280 * public void setTestField(String testField) { // OK, 'testField' param doesn't hide any field 281 * this.field = field; 282 * } 283 * public SomeClass setField(String field) { // OK, 'field' param doesn't hide any field 284 * this.field = field; 285 * } 286 * } 287 * </pre> 288 * <p> 289 * To configure the check so that it ignores parameters of abstract methods: 290 * </p> 291 * <pre> 292 * <module name="HiddenField"> 293 * <property name="ignoreAbstractMethods" value="true"/> 294 * </module> 295 * </pre> 296 * <pre> 297 * abstract class SomeClass { 298 * 299 * private String field; 300 * 301 * public SomeClass(int field) { // violation, 'field' param hides a 'field' field 302 * float field; // violation, 'field' variable hides a 'field' field 303 * } 304 * public abstract int method(String field); // OK 305 * } 306 * 307 * public class Demo extends SomeClass { 308 * 309 * public int method(String param){ 310 * return param; 311 * } 312 * } 313 * </pre> 314 * <p> 315 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 316 * </p> 317 * <p> 318 * Violation Message Keys: 319 * </p> 320 * <ul> 321 * <li> 322 * {@code hidden.field} 323 * </li> 324 * </ul> 325 * 326 * @since 3.0 327 */ 328@FileStatefulCheck 329public class HiddenFieldCheck 330 extends AbstractCheck { 331 332 /** 333 * A key is pointing to the warning message text in "messages.properties" 334 * file. 335 */ 336 public static final String MSG_KEY = "hidden.field"; 337 338 /** 339 * Stack of sets of field names, 340 * one for each class of a set of nested classes. 341 */ 342 private FieldFrame frame; 343 344 /** Define the RegExp for names of variables and parameters to ignore. */ 345 private Pattern ignoreFormat; 346 347 /** 348 * Allow to ignore the parameter of a property setter method. 349 */ 350 private boolean ignoreSetter; 351 352 /** 353 * Allow to expand the definition of a setter method to include methods 354 * that return the class' instance. 355 */ 356 private boolean setterCanReturnItsClass; 357 358 /** Control whether to ignore constructor parameters. */ 359 private boolean ignoreConstructorParameter; 360 361 /** Control whether to ignore parameters of abstract methods. */ 362 private boolean ignoreAbstractMethods; 363 364 @Override 365 public int[] getDefaultTokens() { 366 return getAcceptableTokens(); 367 } 368 369 @Override 370 public int[] getAcceptableTokens() { 371 return new int[] { 372 TokenTypes.VARIABLE_DEF, 373 TokenTypes.PARAMETER_DEF, 374 TokenTypes.CLASS_DEF, 375 TokenTypes.ENUM_DEF, 376 TokenTypes.ENUM_CONSTANT_DEF, 377 TokenTypes.PATTERN_VARIABLE_DEF, 378 TokenTypes.LAMBDA, 379 TokenTypes.RECORD_DEF, 380 TokenTypes.RECORD_COMPONENT_DEF, 381 }; 382 } 383 384 @Override 385 public int[] getRequiredTokens() { 386 return new int[] { 387 TokenTypes.CLASS_DEF, 388 TokenTypes.ENUM_DEF, 389 TokenTypes.ENUM_CONSTANT_DEF, 390 TokenTypes.RECORD_DEF, 391 }; 392 } 393 394 @Override 395 public void beginTree(DetailAST rootAST) { 396 frame = new FieldFrame(null, true, null); 397 } 398 399 @Override 400 public void visitToken(DetailAST ast) { 401 final int type = ast.getType(); 402 switch (type) { 403 case TokenTypes.VARIABLE_DEF: 404 case TokenTypes.PARAMETER_DEF: 405 case TokenTypes.PATTERN_VARIABLE_DEF: 406 case TokenTypes.RECORD_COMPONENT_DEF: 407 processVariable(ast); 408 break; 409 case TokenTypes.LAMBDA: 410 processLambda(ast); 411 break; 412 default: 413 visitOtherTokens(ast, type); 414 } 415 } 416 417 /** 418 * Process a lambda token. 419 * Checks whether a lambda parameter shadows a field. 420 * Note, that when parameter of lambda expression is untyped, 421 * ANTLR parses the parameter as an identifier. 422 * 423 * @param ast the lambda token. 424 */ 425 private void processLambda(DetailAST ast) { 426 final DetailAST firstChild = ast.getFirstChild(); 427 if (firstChild != null 428 && firstChild.getType() == TokenTypes.IDENT) { 429 final String untypedLambdaParameterName = firstChild.getText(); 430 if (frame.containsStaticField(untypedLambdaParameterName) 431 || isInstanceField(firstChild, untypedLambdaParameterName)) { 432 log(firstChild, MSG_KEY, untypedLambdaParameterName); 433 } 434 } 435 } 436 437 /** 438 * Called to process tokens other than {@link TokenTypes#VARIABLE_DEF} 439 * and {@link TokenTypes#PARAMETER_DEF}. 440 * 441 * @param ast token to process 442 * @param type type of the token 443 */ 444 private void visitOtherTokens(DetailAST ast, int type) { 445 // A more thorough check of enum constant class bodies is 446 // possible (checking for hidden fields against the enum 447 // class body in addition to enum constant class bodies) 448 // but not attempted as it seems out of the scope of this 449 // check. 450 final DetailAST typeMods = ast.findFirstToken(TokenTypes.MODIFIERS); 451 final boolean isStaticInnerType = 452 typeMods != null 453 && typeMods.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 454 final String frameName; 455 456 if (type == TokenTypes.CLASS_DEF 457 || type == TokenTypes.ENUM_DEF) { 458 frameName = ast.findFirstToken(TokenTypes.IDENT).getText(); 459 } 460 else { 461 frameName = null; 462 } 463 final FieldFrame newFrame = new FieldFrame(frame, isStaticInnerType, frameName); 464 465 // add fields to container 466 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 467 // enum constants may not have bodies 468 if (objBlock != null) { 469 DetailAST child = objBlock.getFirstChild(); 470 while (child != null) { 471 if (child.getType() == TokenTypes.VARIABLE_DEF) { 472 final String name = 473 child.findFirstToken(TokenTypes.IDENT).getText(); 474 final DetailAST mods = 475 child.findFirstToken(TokenTypes.MODIFIERS); 476 if (mods.findFirstToken(TokenTypes.LITERAL_STATIC) == null) { 477 newFrame.addInstanceField(name); 478 } 479 else { 480 newFrame.addStaticField(name); 481 } 482 } 483 child = child.getNextSibling(); 484 } 485 } 486 if (ast.getType() == TokenTypes.RECORD_DEF) { 487 final DetailAST recordComponents = 488 ast.findFirstToken(TokenTypes.RECORD_COMPONENTS); 489 490 // For each record component definition, we will add it to this frame. 491 TokenUtil.forEachChild(recordComponents, 492 TokenTypes.RECORD_COMPONENT_DEF, node -> { 493 final String name = node.findFirstToken(TokenTypes.IDENT).getText(); 494 newFrame.addInstanceField(name); 495 }); 496 } 497 // push container 498 frame = newFrame; 499 } 500 501 @Override 502 public void leaveToken(DetailAST ast) { 503 if (ast.getType() == TokenTypes.CLASS_DEF 504 || ast.getType() == TokenTypes.ENUM_DEF 505 || ast.getType() == TokenTypes.ENUM_CONSTANT_DEF 506 || ast.getType() == TokenTypes.RECORD_DEF) { 507 // pop 508 frame = frame.getParent(); 509 } 510 } 511 512 /** 513 * Process a variable token. 514 * Check whether a local variable or parameter shadows a field. 515 * Store a field for later comparison with local variables and parameters. 516 * 517 * @param ast the variable token. 518 */ 519 private void processVariable(DetailAST ast) { 520 if (!ScopeUtil.isInInterfaceOrAnnotationBlock(ast) 521 && !CheckUtil.isReceiverParameter(ast) 522 && (ScopeUtil.isLocalVariableDef(ast) 523 || ast.getType() == TokenTypes.PARAMETER_DEF 524 || ast.getType() == TokenTypes.PATTERN_VARIABLE_DEF)) { 525 // local variable or parameter. Does it shadow a field? 526 final DetailAST nameAST = ast.findFirstToken(TokenTypes.IDENT); 527 final String name = nameAST.getText(); 528 529 if ((frame.containsStaticField(name) || isInstanceField(ast, name)) 530 && !isMatchingRegexp(name) 531 && !isIgnoredParam(ast, name)) { 532 log(nameAST, MSG_KEY, name); 533 } 534 } 535 } 536 537 /** 538 * Checks whether method or constructor parameter is ignored. 539 * 540 * @param ast the parameter token. 541 * @param name the parameter name. 542 * @return true if parameter is ignored. 543 */ 544 private boolean isIgnoredParam(DetailAST ast, String name) { 545 return isIgnoredSetterParam(ast, name) 546 || isIgnoredConstructorParam(ast) 547 || isIgnoredParamOfAbstractMethod(ast); 548 } 549 550 /** 551 * Check for instance field. 552 * 553 * @param ast token 554 * @param name identifier of token 555 * @return true if instance field 556 */ 557 private boolean isInstanceField(DetailAST ast, String name) { 558 return !isInStatic(ast) && frame.containsInstanceField(name); 559 } 560 561 /** 562 * Check name by regExp. 563 * 564 * @param name string value to check 565 * @return true is regexp is matching 566 */ 567 private boolean isMatchingRegexp(String name) { 568 return ignoreFormat != null && ignoreFormat.matcher(name).find(); 569 } 570 571 /** 572 * Determines whether an AST node is in a static method or static 573 * initializer. 574 * 575 * @param ast the node to check. 576 * @return true if ast is in a static method or a static block; 577 */ 578 private static boolean isInStatic(DetailAST ast) { 579 DetailAST parent = ast.getParent(); 580 boolean inStatic = false; 581 582 while (parent != null && !inStatic) { 583 if (parent.getType() == TokenTypes.STATIC_INIT) { 584 inStatic = true; 585 } 586 else if (parent.getType() == TokenTypes.METHOD_DEF 587 && !ScopeUtil.isInScope(parent, Scope.ANONINNER) 588 || parent.getType() == TokenTypes.VARIABLE_DEF) { 589 final DetailAST mods = 590 parent.findFirstToken(TokenTypes.MODIFIERS); 591 inStatic = mods.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 592 break; 593 } 594 else { 595 parent = parent.getParent(); 596 } 597 } 598 return inStatic; 599 } 600 601 /** 602 * Decides whether to ignore an AST node that is the parameter of a 603 * setter method, where the property setter method for field 'xyz' has 604 * name 'setXyz', one parameter named 'xyz', and return type void 605 * (default behavior) or return type is name of the class in which 606 * such method is declared (allowed only if 607 * {@link #setSetterCanReturnItsClass(boolean)} is called with 608 * value <em>true</em>). 609 * 610 * @param ast the AST to check. 611 * @param name the name of ast. 612 * @return true if ast should be ignored because check property 613 * ignoreSetter is true and ast is the parameter of a setter method. 614 */ 615 private boolean isIgnoredSetterParam(DetailAST ast, String name) { 616 boolean isIgnoredSetterParam = false; 617 if (ignoreSetter && ast.getType() == TokenTypes.PARAMETER_DEF) { 618 final DetailAST parametersAST = ast.getParent(); 619 final DetailAST methodAST = parametersAST.getParent(); 620 if (parametersAST.getChildCount() == 1 621 && methodAST.getType() == TokenTypes.METHOD_DEF 622 && isSetterMethod(methodAST, name)) { 623 isIgnoredSetterParam = true; 624 } 625 } 626 return isIgnoredSetterParam; 627 } 628 629 /** 630 * Determine if a specific method identified by methodAST and a single 631 * variable name aName is a setter. This recognition partially depends 632 * on mSetterCanReturnItsClass property. 633 * 634 * @param aMethodAST AST corresponding to a method call 635 * @param aName name of single parameter of this method. 636 * @return true of false indicating of method is a setter or not. 637 */ 638 private boolean isSetterMethod(DetailAST aMethodAST, String aName) { 639 final String methodName = 640 aMethodAST.findFirstToken(TokenTypes.IDENT).getText(); 641 boolean isSetterMethod = false; 642 643 if (("set" + capitalize(aName)).equals(methodName)) { 644 // method name did match set${Name}(${anyType} ${aName}) 645 // where ${Name} is capitalized version of ${aName} 646 // therefore this method is potentially a setter 647 final DetailAST typeAST = aMethodAST.findFirstToken(TokenTypes.TYPE); 648 final String returnType = typeAST.getFirstChild().getText(); 649 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) != null 650 || setterCanReturnItsClass && frame.isEmbeddedIn(returnType)) { 651 // this method has signature 652 // 653 // void set${Name}(${anyType} ${name}) 654 // 655 // and therefore considered to be a setter 656 // 657 // or 658 // 659 // return type is not void, but it is the same as the class 660 // where method is declared and and mSetterCanReturnItsClass 661 // is set to true 662 isSetterMethod = true; 663 } 664 } 665 666 return isSetterMethod; 667 } 668 669 /** 670 * Capitalizes a given property name the way we expect to see it in 671 * a setter name. 672 * 673 * @param name a property name 674 * @return capitalized property name 675 */ 676 private static String capitalize(final String name) { 677 String setterName = name; 678 // we should not capitalize the first character if the second 679 // one is a capital one, since according to JavaBeans spec 680 // setXYzz() is a setter for XYzz property, not for xYzz one. 681 if (name.length() == 1 || !Character.isUpperCase(name.charAt(1))) { 682 setterName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1); 683 } 684 return setterName; 685 } 686 687 /** 688 * Decides whether to ignore an AST node that is the parameter of a 689 * constructor. 690 * 691 * @param ast the AST to check. 692 * @return true if ast should be ignored because check property 693 * ignoreConstructorParameter is true and ast is a constructor parameter. 694 */ 695 private boolean isIgnoredConstructorParam(DetailAST ast) { 696 boolean result = false; 697 if (ignoreConstructorParameter 698 && ast.getType() == TokenTypes.PARAMETER_DEF) { 699 final DetailAST parametersAST = ast.getParent(); 700 final DetailAST constructorAST = parametersAST.getParent(); 701 result = constructorAST.getType() == TokenTypes.CTOR_DEF; 702 } 703 return result; 704 } 705 706 /** 707 * Decides whether to ignore an AST node that is the parameter of an 708 * abstract method. 709 * 710 * @param ast the AST to check. 711 * @return true if ast should be ignored because check property 712 * ignoreAbstractMethods is true and ast is a parameter of abstract methods. 713 */ 714 private boolean isIgnoredParamOfAbstractMethod(DetailAST ast) { 715 boolean result = false; 716 if (ignoreAbstractMethods 717 && ast.getType() == TokenTypes.PARAMETER_DEF) { 718 final DetailAST method = ast.getParent().getParent(); 719 if (method.getType() == TokenTypes.METHOD_DEF) { 720 final DetailAST mods = method.findFirstToken(TokenTypes.MODIFIERS); 721 result = mods.findFirstToken(TokenTypes.ABSTRACT) != null; 722 } 723 } 724 return result; 725 } 726 727 /** 728 * Setter to define the RegExp for names of variables and parameters to ignore. 729 * 730 * @param pattern a pattern. 731 */ 732 public void setIgnoreFormat(Pattern pattern) { 733 ignoreFormat = pattern; 734 } 735 736 /** 737 * Setter to allow to ignore the parameter of a property setter method. 738 * 739 * @param ignoreSetter decide whether to ignore the parameter of 740 * a property setter method. 741 */ 742 public void setIgnoreSetter(boolean ignoreSetter) { 743 this.ignoreSetter = ignoreSetter; 744 } 745 746 /** 747 * Setter to allow to expand the definition of a setter method to include methods 748 * that return the class' instance. 749 * 750 * @param aSetterCanReturnItsClass if true then setter can return 751 * either void or class in which it is declared. If false then 752 * in order to be recognized as setter method (otherwise 753 * already recognized as a setter) must return void. Later is 754 * the default behavior. 755 */ 756 public void setSetterCanReturnItsClass( 757 boolean aSetterCanReturnItsClass) { 758 setterCanReturnItsClass = aSetterCanReturnItsClass; 759 } 760 761 /** 762 * Setter to control whether to ignore constructor parameters. 763 * 764 * @param ignoreConstructorParameter decide whether to ignore 765 * constructor parameters. 766 */ 767 public void setIgnoreConstructorParameter( 768 boolean ignoreConstructorParameter) { 769 this.ignoreConstructorParameter = ignoreConstructorParameter; 770 } 771 772 /** 773 * Setter to control whether to ignore parameters of abstract methods. 774 * 775 * @param ignoreAbstractMethods decide whether to ignore 776 * parameters of abstract methods. 777 */ 778 public void setIgnoreAbstractMethods( 779 boolean ignoreAbstractMethods) { 780 this.ignoreAbstractMethods = ignoreAbstractMethods; 781 } 782 783 /** 784 * Holds the names of static and instance fields of a type. 785 */ 786 private static class FieldFrame { 787 788 /** Name of the frame, such name of the class or enum declaration. */ 789 private final String frameName; 790 791 /** Is this a static inner type. */ 792 private final boolean staticType; 793 794 /** Parent frame. */ 795 private final FieldFrame parent; 796 797 /** Set of instance field names. */ 798 private final Set<String> instanceFields = new HashSet<>(); 799 800 /** Set of static field names. */ 801 private final Set<String> staticFields = new HashSet<>(); 802 803 /** 804 * Creates new frame. 805 * 806 * @param parent parent frame. 807 * @param staticType is this a static inner type (class or enum). 808 * @param frameName name associated with the frame, which can be a 809 */ 810 /* package */ FieldFrame(FieldFrame parent, boolean staticType, String frameName) { 811 this.parent = parent; 812 this.staticType = staticType; 813 this.frameName = frameName; 814 } 815 816 /** 817 * Adds an instance field to this FieldFrame. 818 * 819 * @param field the name of the instance field. 820 */ 821 public void addInstanceField(String field) { 822 instanceFields.add(field); 823 } 824 825 /** 826 * Adds a static field to this FieldFrame. 827 * 828 * @param field the name of the instance field. 829 */ 830 public void addStaticField(String field) { 831 staticFields.add(field); 832 } 833 834 /** 835 * Determines whether this FieldFrame contains an instance field. 836 * 837 * @param field the field to check. 838 * @return true if this FieldFrame contains instance field field. 839 */ 840 public boolean containsInstanceField(String field) { 841 return instanceFields.contains(field) 842 || parent != null 843 && !staticType 844 && parent.containsInstanceField(field); 845 } 846 847 /** 848 * Determines whether this FieldFrame contains a static field. 849 * 850 * @param field the field to check. 851 * @return true if this FieldFrame contains static field field. 852 */ 853 public boolean containsStaticField(String field) { 854 return staticFields.contains(field) 855 || parent != null 856 && parent.containsStaticField(field); 857 } 858 859 /** 860 * Getter for parent frame. 861 * 862 * @return parent frame. 863 */ 864 public FieldFrame getParent() { 865 return parent; 866 } 867 868 /** 869 * Check if current frame is embedded in class or enum with 870 * specific name. 871 * 872 * @param classOrEnumName name of class or enum that we are looking 873 * for in the chain of field frames. 874 * 875 * @return true if current frame is embedded in class or enum 876 * with name classOrNameName 877 */ 878 private boolean isEmbeddedIn(String classOrEnumName) { 879 FieldFrame currentFrame = this; 880 boolean isEmbeddedIn = false; 881 while (currentFrame != null) { 882 if (Objects.equals(currentFrame.frameName, classOrEnumName)) { 883 isEmbeddedIn = true; 884 break; 885 } 886 currentFrame = currentFrame.parent; 887 } 888 return isEmbeddedIn; 889 } 890 891 } 892 893}