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