001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 * 129 * <p> 130 * To configure the check so that it checks local variables but not parameters: 131 * </p> 132 * <pre> 133 * <module name="HiddenField"> 134 * <property name="tokens" value="VARIABLE_DEF"/> 135 * </module> 136 * </pre> 137 * 138 * <p> 139 * To configure the check so that it ignores the variables and parameters named "test": 140 * </p> 141 * <pre> 142 * <module name="HiddenField"> 143 * <property name="ignoreFormat" value="^test$"/> 144 * </module> 145 * </pre> 146 * <pre> 147 * class SomeClass 148 * { 149 * private List<String> test; 150 * 151 * private void addTest(List<String> test) // no violation 152 * { 153 * this.test.addAll(test); 154 * } 155 * 156 * private void foo() 157 * { 158 * final List<String> test = new ArrayList<>(); // no violation 159 * ... 160 * } 161 * } 162 * </pre> 163 * <p> 164 * To configure the check so that it ignores constructor parameters: 165 * </p> 166 * <pre> 167 * <module name="HiddenField"> 168 * <property name="ignoreConstructorParameter" value="true"/> 169 * </module> 170 * </pre> 171 * <p> 172 * To configure the check so that it ignores the parameter of setter methods: 173 * </p> 174 * <pre> 175 * <module name="HiddenField"> 176 * <property name="ignoreSetter" value="true"/> 177 * </module> 178 * </pre> 179 * <p> 180 * To configure the check so that it ignores the parameter of setter methods 181 * recognizing setter as returning either {@code void} or a class in which it is declared: 182 * </p> 183 * <pre> 184 * <module name="HiddenField"> 185 * <property name="ignoreSetter" value="true"/> 186 * <property name="setterCanReturnItsClass" value="true"/> 187 * </module> 188 * </pre> 189 * <p> 190 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 191 * </p> 192 * <p> 193 * Violation Message Keys: 194 * </p> 195 * <ul> 196 * <li> 197 * {@code hidden.field} 198 * </li> 199 * </ul> 200 * 201 * @since 3.0 202 */ 203@FileStatefulCheck 204public class HiddenFieldCheck 205 extends AbstractCheck { 206 207 /** 208 * A key is pointing to the warning message text in "messages.properties" 209 * file. 210 */ 211 public static final String MSG_KEY = "hidden.field"; 212 213 /** 214 * Stack of sets of field names, 215 * one for each class of a set of nested classes. 216 */ 217 private FieldFrame frame; 218 219 /** Define the RegExp for names of variables and parameters to ignore. */ 220 private Pattern ignoreFormat; 221 222 /** 223 * Allow to ignore the parameter of a property setter method. 224 */ 225 private boolean ignoreSetter; 226 227 /** 228 * Allow to expand the definition of a setter method to include methods 229 * that return the class' instance. 230 */ 231 private boolean setterCanReturnItsClass; 232 233 /** Control whether to ignore constructor parameters. */ 234 private boolean ignoreConstructorParameter; 235 236 /** Control whether to ignore parameters of abstract methods. */ 237 private boolean ignoreAbstractMethods; 238 239 @Override 240 public int[] getDefaultTokens() { 241 return getAcceptableTokens(); 242 } 243 244 @Override 245 public int[] getAcceptableTokens() { 246 return new int[] { 247 TokenTypes.VARIABLE_DEF, 248 TokenTypes.PARAMETER_DEF, 249 TokenTypes.CLASS_DEF, 250 TokenTypes.ENUM_DEF, 251 TokenTypes.ENUM_CONSTANT_DEF, 252 TokenTypes.PATTERN_VARIABLE_DEF, 253 TokenTypes.LAMBDA, 254 TokenTypes.RECORD_DEF, 255 TokenTypes.RECORD_COMPONENT_DEF, 256 }; 257 } 258 259 @Override 260 public int[] getRequiredTokens() { 261 return new int[] { 262 TokenTypes.CLASS_DEF, 263 TokenTypes.ENUM_DEF, 264 TokenTypes.ENUM_CONSTANT_DEF, 265 TokenTypes.RECORD_DEF, 266 }; 267 } 268 269 @Override 270 public void beginTree(DetailAST rootAST) { 271 frame = new FieldFrame(null, true, null); 272 } 273 274 @Override 275 public void visitToken(DetailAST ast) { 276 final int type = ast.getType(); 277 switch (type) { 278 case TokenTypes.VARIABLE_DEF: 279 case TokenTypes.PARAMETER_DEF: 280 case TokenTypes.PATTERN_VARIABLE_DEF: 281 case TokenTypes.RECORD_COMPONENT_DEF: 282 processVariable(ast); 283 break; 284 case TokenTypes.LAMBDA: 285 processLambda(ast); 286 break; 287 default: 288 visitOtherTokens(ast, type); 289 } 290 } 291 292 /** 293 * Process a lambda token. 294 * Checks whether a lambda parameter shadows a field. 295 * Note, that when parameter of lambda expression is untyped, 296 * ANTLR parses the parameter as an identifier. 297 * 298 * @param ast the lambda token. 299 */ 300 private void processLambda(DetailAST ast) { 301 final DetailAST firstChild = ast.getFirstChild(); 302 if (firstChild != null 303 && firstChild.getType() == TokenTypes.IDENT) { 304 final String untypedLambdaParameterName = firstChild.getText(); 305 if (frame.containsStaticField(untypedLambdaParameterName) 306 || isInstanceField(firstChild, untypedLambdaParameterName)) { 307 log(firstChild, MSG_KEY, untypedLambdaParameterName); 308 } 309 } 310 } 311 312 /** 313 * Called to process tokens other than {@link TokenTypes#VARIABLE_DEF} 314 * and {@link TokenTypes#PARAMETER_DEF}. 315 * 316 * @param ast token to process 317 * @param type type of the token 318 */ 319 private void visitOtherTokens(DetailAST ast, int type) { 320 // A more thorough check of enum constant class bodies is 321 // possible (checking for hidden fields against the enum 322 // class body in addition to enum constant class bodies) 323 // but not attempted as it seems out of the scope of this 324 // check. 325 final DetailAST typeMods = ast.findFirstToken(TokenTypes.MODIFIERS); 326 final boolean isStaticInnerType = 327 typeMods != null 328 && typeMods.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 329 final String frameName; 330 331 if (type == TokenTypes.CLASS_DEF 332 || type == TokenTypes.ENUM_DEF) { 333 frameName = ast.findFirstToken(TokenTypes.IDENT).getText(); 334 } 335 else { 336 frameName = null; 337 } 338 final FieldFrame newFrame = new FieldFrame(frame, isStaticInnerType, frameName); 339 340 // add fields to container 341 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 342 // enum constants may not have bodies 343 if (objBlock != null) { 344 DetailAST child = objBlock.getFirstChild(); 345 while (child != null) { 346 if (child.getType() == TokenTypes.VARIABLE_DEF) { 347 final String name = 348 child.findFirstToken(TokenTypes.IDENT).getText(); 349 final DetailAST mods = 350 child.findFirstToken(TokenTypes.MODIFIERS); 351 if (mods.findFirstToken(TokenTypes.LITERAL_STATIC) == null) { 352 newFrame.addInstanceField(name); 353 } 354 else { 355 newFrame.addStaticField(name); 356 } 357 } 358 child = child.getNextSibling(); 359 } 360 } 361 if (ast.getType() == TokenTypes.RECORD_DEF) { 362 final DetailAST recordComponents = 363 ast.findFirstToken(TokenTypes.RECORD_COMPONENTS); 364 365 // For each record component definition, we will add it to this frame. 366 TokenUtil.forEachChild(recordComponents, 367 TokenTypes.RECORD_COMPONENT_DEF, node -> { 368 final String name = node.findFirstToken(TokenTypes.IDENT).getText(); 369 newFrame.addInstanceField(name); 370 }); 371 } 372 // push container 373 frame = newFrame; 374 } 375 376 @Override 377 public void leaveToken(DetailAST ast) { 378 if (ast.getType() == TokenTypes.CLASS_DEF 379 || ast.getType() == TokenTypes.ENUM_DEF 380 || ast.getType() == TokenTypes.ENUM_CONSTANT_DEF 381 || ast.getType() == TokenTypes.RECORD_DEF) { 382 // pop 383 frame = frame.getParent(); 384 } 385 } 386 387 /** 388 * Process a variable token. 389 * Check whether a local variable or parameter shadows a field. 390 * Store a field for later comparison with local variables and parameters. 391 * 392 * @param ast the variable token. 393 */ 394 private void processVariable(DetailAST ast) { 395 if (!ScopeUtil.isInInterfaceOrAnnotationBlock(ast) 396 && !CheckUtil.isReceiverParameter(ast) 397 && (ScopeUtil.isLocalVariableDef(ast) 398 || ast.getType() == TokenTypes.PARAMETER_DEF 399 || ast.getType() == TokenTypes.PATTERN_VARIABLE_DEF)) { 400 // local variable or parameter. Does it shadow a field? 401 final DetailAST nameAST = ast.findFirstToken(TokenTypes.IDENT); 402 final String name = nameAST.getText(); 403 404 if ((frame.containsStaticField(name) || isInstanceField(ast, name)) 405 && !isMatchingRegexp(name) 406 && !isIgnoredParam(ast, name)) { 407 log(nameAST, MSG_KEY, name); 408 } 409 } 410 } 411 412 /** 413 * Checks whether method or constructor parameter is ignored. 414 * 415 * @param ast the parameter token. 416 * @param name the parameter name. 417 * @return true if parameter is ignored. 418 */ 419 private boolean isIgnoredParam(DetailAST ast, String name) { 420 return isIgnoredSetterParam(ast, name) 421 || isIgnoredConstructorParam(ast) 422 || isIgnoredParamOfAbstractMethod(ast); 423 } 424 425 /** 426 * Check for instance field. 427 * 428 * @param ast token 429 * @param name identifier of token 430 * @return true if instance field 431 */ 432 private boolean isInstanceField(DetailAST ast, String name) { 433 return !isInStatic(ast) && frame.containsInstanceField(name); 434 } 435 436 /** 437 * Check name by regExp. 438 * 439 * @param name string value to check 440 * @return true is regexp is matching 441 */ 442 private boolean isMatchingRegexp(String name) { 443 return ignoreFormat != null && ignoreFormat.matcher(name).find(); 444 } 445 446 /** 447 * Determines whether an AST node is in a static method or static 448 * initializer. 449 * 450 * @param ast the node to check. 451 * @return true if ast is in a static method or a static block; 452 */ 453 private static boolean isInStatic(DetailAST ast) { 454 DetailAST parent = ast.getParent(); 455 boolean inStatic = false; 456 457 while (parent != null && !inStatic) { 458 if (parent.getType() == TokenTypes.STATIC_INIT) { 459 inStatic = true; 460 } 461 else if (parent.getType() == TokenTypes.METHOD_DEF 462 && !ScopeUtil.isInScope(parent, Scope.ANONINNER) 463 || parent.getType() == TokenTypes.VARIABLE_DEF) { 464 final DetailAST mods = 465 parent.findFirstToken(TokenTypes.MODIFIERS); 466 inStatic = mods.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 467 break; 468 } 469 else { 470 parent = parent.getParent(); 471 } 472 } 473 return inStatic; 474 } 475 476 /** 477 * Decides whether to ignore an AST node that is the parameter of a 478 * setter method, where the property setter method for field 'xyz' has 479 * name 'setXyz', one parameter named 'xyz', and return type void 480 * (default behavior) or return type is name of the class in which 481 * such method is declared (allowed only if 482 * {@link #setSetterCanReturnItsClass(boolean)} is called with 483 * value <em>true</em>). 484 * 485 * @param ast the AST to check. 486 * @param name the name of ast. 487 * @return true if ast should be ignored because check property 488 * ignoreSetter is true and ast is the parameter of a setter method. 489 */ 490 private boolean isIgnoredSetterParam(DetailAST ast, String name) { 491 boolean isIgnoredSetterParam = false; 492 if (ignoreSetter && ast.getType() == TokenTypes.PARAMETER_DEF) { 493 final DetailAST parametersAST = ast.getParent(); 494 final DetailAST methodAST = parametersAST.getParent(); 495 if (parametersAST.getChildCount() == 1 496 && methodAST.getType() == TokenTypes.METHOD_DEF 497 && isSetterMethod(methodAST, name)) { 498 isIgnoredSetterParam = true; 499 } 500 } 501 return isIgnoredSetterParam; 502 } 503 504 /** 505 * Determine if a specific method identified by methodAST and a single 506 * variable name aName is a setter. This recognition partially depends 507 * on mSetterCanReturnItsClass property. 508 * 509 * @param aMethodAST AST corresponding to a method call 510 * @param aName name of single parameter of this method. 511 * @return true of false indicating of method is a setter or not. 512 */ 513 private boolean isSetterMethod(DetailAST aMethodAST, String aName) { 514 final String methodName = 515 aMethodAST.findFirstToken(TokenTypes.IDENT).getText(); 516 boolean isSetterMethod = false; 517 518 if (("set" + capitalize(aName)).equals(methodName)) { 519 // method name did match set${Name}(${anyType} ${aName}) 520 // where ${Name} is capitalized version of ${aName} 521 // therefore this method is potentially a setter 522 final DetailAST typeAST = aMethodAST.findFirstToken(TokenTypes.TYPE); 523 final String returnType = typeAST.getFirstChild().getText(); 524 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) != null 525 || setterCanReturnItsClass && frame.isEmbeddedIn(returnType)) { 526 // this method has signature 527 // 528 // void set${Name}(${anyType} ${name}) 529 // 530 // and therefore considered to be a setter 531 // 532 // or 533 // 534 // return type is not void, but it is the same as the class 535 // where method is declared and and mSetterCanReturnItsClass 536 // is set to true 537 isSetterMethod = true; 538 } 539 } 540 541 return isSetterMethod; 542 } 543 544 /** 545 * Capitalizes a given property name the way we expect to see it in 546 * a setter name. 547 * 548 * @param name a property name 549 * @return capitalized property name 550 */ 551 private static String capitalize(final String name) { 552 String setterName = name; 553 // we should not capitalize the first character if the second 554 // one is a capital one, since according to JavaBeans spec 555 // setXYzz() is a setter for XYzz property, not for xYzz one. 556 if (name.length() == 1 || !Character.isUpperCase(name.charAt(1))) { 557 setterName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1); 558 } 559 return setterName; 560 } 561 562 /** 563 * Decides whether to ignore an AST node that is the parameter of a 564 * constructor. 565 * 566 * @param ast the AST to check. 567 * @return true if ast should be ignored because check property 568 * ignoreConstructorParameter is true and ast is a constructor parameter. 569 */ 570 private boolean isIgnoredConstructorParam(DetailAST ast) { 571 boolean result = false; 572 if (ignoreConstructorParameter 573 && ast.getType() == TokenTypes.PARAMETER_DEF) { 574 final DetailAST parametersAST = ast.getParent(); 575 final DetailAST constructorAST = parametersAST.getParent(); 576 result = constructorAST.getType() == TokenTypes.CTOR_DEF; 577 } 578 return result; 579 } 580 581 /** 582 * Decides whether to ignore an AST node that is the parameter of an 583 * abstract method. 584 * 585 * @param ast the AST to check. 586 * @return true if ast should be ignored because check property 587 * ignoreAbstractMethods is true and ast is a parameter of abstract methods. 588 */ 589 private boolean isIgnoredParamOfAbstractMethod(DetailAST ast) { 590 boolean result = false; 591 if (ignoreAbstractMethods 592 && ast.getType() == TokenTypes.PARAMETER_DEF) { 593 final DetailAST method = ast.getParent().getParent(); 594 if (method.getType() == TokenTypes.METHOD_DEF) { 595 final DetailAST mods = method.findFirstToken(TokenTypes.MODIFIERS); 596 result = mods.findFirstToken(TokenTypes.ABSTRACT) != null; 597 } 598 } 599 return result; 600 } 601 602 /** 603 * Setter to define the RegExp for names of variables and parameters to ignore. 604 * 605 * @param pattern a pattern. 606 */ 607 public void setIgnoreFormat(Pattern pattern) { 608 ignoreFormat = pattern; 609 } 610 611 /** 612 * Setter to allow to ignore the parameter of a property setter method. 613 * 614 * @param ignoreSetter decide whether to ignore the parameter of 615 * a property setter method. 616 */ 617 public void setIgnoreSetter(boolean ignoreSetter) { 618 this.ignoreSetter = ignoreSetter; 619 } 620 621 /** 622 * Setter to allow to expand the definition of a setter method to include methods 623 * that return the class' instance. 624 * 625 * @param aSetterCanReturnItsClass if true then setter can return 626 * either void or class in which it is declared. If false then 627 * in order to be recognized as setter method (otherwise 628 * already recognized as a setter) must return void. Later is 629 * the default behavior. 630 */ 631 public void setSetterCanReturnItsClass( 632 boolean aSetterCanReturnItsClass) { 633 setterCanReturnItsClass = aSetterCanReturnItsClass; 634 } 635 636 /** 637 * Setter to control whether to ignore constructor parameters. 638 * 639 * @param ignoreConstructorParameter decide whether to ignore 640 * constructor parameters. 641 */ 642 public void setIgnoreConstructorParameter( 643 boolean ignoreConstructorParameter) { 644 this.ignoreConstructorParameter = ignoreConstructorParameter; 645 } 646 647 /** 648 * Setter to control whether to ignore parameters of abstract methods. 649 * 650 * @param ignoreAbstractMethods decide whether to ignore 651 * parameters of abstract methods. 652 */ 653 public void setIgnoreAbstractMethods( 654 boolean ignoreAbstractMethods) { 655 this.ignoreAbstractMethods = ignoreAbstractMethods; 656 } 657 658 /** 659 * Holds the names of static and instance fields of a type. 660 */ 661 private static class FieldFrame { 662 663 /** Name of the frame, such name of the class or enum declaration. */ 664 private final String frameName; 665 666 /** Is this a static inner type. */ 667 private final boolean staticType; 668 669 /** Parent frame. */ 670 private final FieldFrame parent; 671 672 /** Set of instance field names. */ 673 private final Set<String> instanceFields = new HashSet<>(); 674 675 /** Set of static field names. */ 676 private final Set<String> staticFields = new HashSet<>(); 677 678 /** 679 * Creates new frame. 680 * 681 * @param parent parent frame. 682 * @param staticType is this a static inner type (class or enum). 683 * @param frameName name associated with the frame, which can be a 684 */ 685 /* package */ FieldFrame(FieldFrame parent, boolean staticType, String frameName) { 686 this.parent = parent; 687 this.staticType = staticType; 688 this.frameName = frameName; 689 } 690 691 /** 692 * Adds an instance field to this FieldFrame. 693 * 694 * @param field the name of the instance field. 695 */ 696 public void addInstanceField(String field) { 697 instanceFields.add(field); 698 } 699 700 /** 701 * Adds a static field to this FieldFrame. 702 * 703 * @param field the name of the instance field. 704 */ 705 public void addStaticField(String field) { 706 staticFields.add(field); 707 } 708 709 /** 710 * Determines whether this FieldFrame contains an instance field. 711 * 712 * @param field the field to check. 713 * @return true if this FieldFrame contains instance field field. 714 */ 715 public boolean containsInstanceField(String field) { 716 return instanceFields.contains(field) 717 || parent != null 718 && !staticType 719 && parent.containsInstanceField(field); 720 } 721 722 /** 723 * Determines whether this FieldFrame contains a static field. 724 * 725 * @param field the field to check. 726 * @return true if this FieldFrame contains static field field. 727 */ 728 public boolean containsStaticField(String field) { 729 return staticFields.contains(field) 730 || parent != null 731 && parent.containsStaticField(field); 732 } 733 734 /** 735 * Getter for parent frame. 736 * 737 * @return parent frame. 738 */ 739 public FieldFrame getParent() { 740 return parent; 741 } 742 743 /** 744 * Check if current frame is embedded in class or enum with 745 * specific name. 746 * 747 * @param classOrEnumName name of class or enum that we are looking 748 * for in the chain of field frames. 749 * 750 * @return true if current frame is embedded in class or enum 751 * with name classOrNameName 752 */ 753 private boolean isEmbeddedIn(String classOrEnumName) { 754 FieldFrame currentFrame = this; 755 boolean isEmbeddedIn = false; 756 while (currentFrame != null) { 757 if (Objects.equals(currentFrame.frameName, classOrEnumName)) { 758 isEmbeddedIn = true; 759 break; 760 } 761 currentFrame = currentFrame.parent; 762 } 763 return isEmbeddedIn; 764 } 765 766 } 767 768}