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 TokenTypes.PARAMETER_DEF, 221 TokenTypes.PATTERN_VARIABLE_DEF, 222 TokenTypes.RECORD_COMPONENT_DEF -> processVariable(ast); 223 case TokenTypes.LAMBDA -> processLambda(ast); 224 default -> visitOtherTokens(ast, type); 225 } 226 } 227 228 /** 229 * Process a lambda token. 230 * Checks whether a lambda parameter shadows a field. 231 * Note, that when parameter of lambda expression is untyped, 232 * ANTLR parses the parameter as an identifier. 233 * 234 * @param ast the lambda token. 235 */ 236 private void processLambda(DetailAST ast) { 237 final DetailAST firstChild = ast.getFirstChild(); 238 if (TokenUtil.isOfType(firstChild, TokenTypes.IDENT)) { 239 final String untypedLambdaParameterName = firstChild.getText(); 240 if (frame.containsStaticField(untypedLambdaParameterName) 241 || isInstanceField(firstChild, untypedLambdaParameterName)) { 242 log(firstChild, MSG_KEY, untypedLambdaParameterName); 243 } 244 } 245 } 246 247 /** 248 * Called to process tokens other than {@link TokenTypes#VARIABLE_DEF} 249 * and {@link TokenTypes#PARAMETER_DEF}. 250 * 251 * @param ast token to process 252 * @param type type of the token 253 */ 254 private void visitOtherTokens(DetailAST ast, int type) { 255 // A more thorough check of enum constant class bodies is 256 // possible (checking for hidden fields against the enum 257 // class body in addition to enum constant class bodies) 258 // but not attempted as it seems out of the scope of this 259 // check. 260 final DetailAST typeMods = ast.findFirstToken(TokenTypes.MODIFIERS); 261 final boolean isStaticInnerType = 262 typeMods != null 263 && typeMods.findFirstToken(TokenTypes.LITERAL_STATIC) != null 264 // inner record is implicitly static 265 || ast.getType() == TokenTypes.RECORD_DEF; 266 final String frameName; 267 268 if (type == TokenTypes.CLASS_DEF 269 || type == TokenTypes.ENUM_DEF) { 270 frameName = ast.findFirstToken(TokenTypes.IDENT).getText(); 271 } 272 else { 273 frameName = null; 274 } 275 final FieldFrame newFrame = new FieldFrame(frame, isStaticInnerType, frameName); 276 277 // add fields to container 278 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 279 // enum constants may not have bodies 280 if (objBlock != null) { 281 DetailAST child = objBlock.getFirstChild(); 282 while (child != null) { 283 if (child.getType() == TokenTypes.VARIABLE_DEF) { 284 final String name = 285 child.findFirstToken(TokenTypes.IDENT).getText(); 286 final DetailAST mods = 287 child.findFirstToken(TokenTypes.MODIFIERS); 288 if (mods.findFirstToken(TokenTypes.LITERAL_STATIC) == null) { 289 newFrame.addInstanceField(name); 290 } 291 else { 292 newFrame.addStaticField(name); 293 } 294 } 295 child = child.getNextSibling(); 296 } 297 } 298 if (ast.getType() == TokenTypes.RECORD_DEF) { 299 final DetailAST recordComponents = 300 ast.findFirstToken(TokenTypes.RECORD_COMPONENTS); 301 302 // For each record component definition, we will add it to this frame. 303 TokenUtil.forEachChild(recordComponents, 304 TokenTypes.RECORD_COMPONENT_DEF, node -> { 305 final String name = node.findFirstToken(TokenTypes.IDENT).getText(); 306 newFrame.addInstanceField(name); 307 }); 308 } 309 // push container 310 frame = newFrame; 311 } 312 313 @Override 314 public void leaveToken(DetailAST ast) { 315 if (ast.getType() == TokenTypes.CLASS_DEF 316 || ast.getType() == TokenTypes.ENUM_DEF 317 || ast.getType() == TokenTypes.ENUM_CONSTANT_DEF 318 || ast.getType() == TokenTypes.RECORD_DEF) { 319 // pop 320 frame = frame.getParent(); 321 } 322 } 323 324 /** 325 * Process a variable token. 326 * Check whether a local variable or parameter shadows a field. 327 * Store a field for later comparison with local variables and parameters. 328 * 329 * @param ast the variable token. 330 */ 331 private void processVariable(DetailAST ast) { 332 if (!ScopeUtil.isInInterfaceOrAnnotationBlock(ast) 333 && !CheckUtil.isReceiverParameter(ast) 334 && (ScopeUtil.isLocalVariableDef(ast) 335 || ast.getType() == TokenTypes.PARAMETER_DEF 336 || ast.getType() == TokenTypes.PATTERN_VARIABLE_DEF)) { 337 // local variable or parameter. Does it shadow a field? 338 final DetailAST nameAST = ast.findFirstToken(TokenTypes.IDENT); 339 final String name = nameAST.getText(); 340 341 if ((frame.containsStaticField(name) || isInstanceField(ast, name)) 342 && !isMatchingRegexp(name) 343 && !isIgnoredParam(ast, name)) { 344 log(nameAST, MSG_KEY, name); 345 } 346 } 347 } 348 349 /** 350 * Checks whether method or constructor parameter is ignored. 351 * 352 * @param ast the parameter token. 353 * @param name the parameter name. 354 * @return true if parameter is ignored. 355 */ 356 private boolean isIgnoredParam(DetailAST ast, String name) { 357 return isIgnoredSetterParam(ast, name) 358 || isIgnoredConstructorParam(ast) 359 || isIgnoredParamOfAbstractMethod(ast); 360 } 361 362 /** 363 * Check for instance field. 364 * 365 * @param ast token 366 * @param name identifier of token 367 * @return true if instance field 368 */ 369 private boolean isInstanceField(DetailAST ast, String name) { 370 return !isInStatic(ast) && frame.containsInstanceField(name); 371 } 372 373 /** 374 * Check name by regExp. 375 * 376 * @param name string value to check 377 * @return true is regexp is matching 378 */ 379 private boolean isMatchingRegexp(String name) { 380 return ignoreFormat != null && ignoreFormat.matcher(name).find(); 381 } 382 383 /** 384 * Determines whether an AST node is in a static method or static 385 * initializer. 386 * 387 * @param ast the node to check. 388 * @return true if ast is in a static method or a static block; 389 */ 390 private static boolean isInStatic(DetailAST ast) { 391 DetailAST parent = ast.getParent(); 392 boolean inStatic = false; 393 394 while (parent != null && !inStatic) { 395 if (parent.getType() == TokenTypes.STATIC_INIT) { 396 inStatic = true; 397 } 398 else if (parent.getType() == TokenTypes.METHOD_DEF 399 && !ScopeUtil.isInScope(parent, Scope.ANONINNER) 400 || parent.getType() == TokenTypes.VARIABLE_DEF) { 401 final DetailAST mods = 402 parent.findFirstToken(TokenTypes.MODIFIERS); 403 inStatic = mods.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 404 break; 405 } 406 else { 407 parent = parent.getParent(); 408 } 409 } 410 return inStatic; 411 } 412 413 /** 414 * Decides whether to ignore an AST node that is the parameter of a 415 * setter method, where the property setter method for field 'xyz' has 416 * name 'setXyz', one parameter named 'xyz', and return type void 417 * (default behavior) or return type is name of the class in which 418 * such method is declared (allowed only if 419 * {@link #setSetterCanReturnItsClass(boolean)} is called with 420 * value <em>true</em>). 421 * 422 * @param ast the AST to check. 423 * @param name the name of ast. 424 * @return true if ast should be ignored because check property 425 * ignoreSetter is true and ast is the parameter of a setter method. 426 */ 427 private boolean isIgnoredSetterParam(DetailAST ast, String name) { 428 boolean isIgnoredSetterParam = false; 429 if (ignoreSetter) { 430 final DetailAST parametersAST = ast.getParent(); 431 final DetailAST methodAST = parametersAST.getParent(); 432 if (parametersAST.getChildCount() == 1 433 && methodAST.getType() == TokenTypes.METHOD_DEF 434 && isSetterMethod(methodAST, name)) { 435 isIgnoredSetterParam = true; 436 } 437 } 438 return isIgnoredSetterParam; 439 } 440 441 /** 442 * Determine if a specific method identified by methodAST and a single 443 * variable name aName is a setter. This recognition partially depends 444 * on mSetterCanReturnItsClass property. 445 * 446 * @param aMethodAST AST corresponding to a method call 447 * @param aName name of single parameter of this method. 448 * @return true of false indicating of method is a setter or not. 449 */ 450 private boolean isSetterMethod(DetailAST aMethodAST, String aName) { 451 final String methodName = 452 aMethodAST.findFirstToken(TokenTypes.IDENT).getText(); 453 boolean isSetterMethod = false; 454 455 if (("set" + capitalize(aName)).equals(methodName)) { 456 // method name did match set${Name}(${anyType} ${aName}) 457 // where ${Name} is capitalized version of ${aName} 458 // therefore this method is potentially a setter 459 final DetailAST typeAST = aMethodAST.findFirstToken(TokenTypes.TYPE); 460 final String returnType = typeAST.getFirstChild().getText(); 461 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) != null 462 || setterCanReturnItsClass && frame.isEmbeddedIn(returnType)) { 463 // this method has signature 464 // 465 // void set${Name}(${anyType} ${name}) 466 // 467 // and therefore considered to be a setter 468 // 469 // or 470 // 471 // return type is not void, but it is the same as the class 472 // where method is declared and mSetterCanReturnItsClass 473 // is set to true 474 isSetterMethod = true; 475 } 476 } 477 478 return isSetterMethod; 479 } 480 481 /** 482 * Capitalizes a given property name the way we expect to see it in 483 * a setter name. 484 * 485 * @param name a property name 486 * @return capitalized property name 487 */ 488 private static String capitalize(final String name) { 489 String setterName = name; 490 // we should not capitalize the first character if the second 491 // one is a capital one, since according to JavaBeans spec 492 // setXYzz() is a setter for XYzz property, not for xYzz one. 493 if (name.length() == 1 || !Character.isUpperCase(name.charAt(1))) { 494 setterName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1); 495 } 496 return setterName; 497 } 498 499 /** 500 * Decides whether to ignore an AST node that is the parameter of a 501 * constructor. 502 * 503 * @param ast the AST to check. 504 * @return true if ast should be ignored because check property 505 * ignoreConstructorParameter is true and ast is a constructor parameter. 506 */ 507 private boolean isIgnoredConstructorParam(DetailAST ast) { 508 boolean result = false; 509 if (ignoreConstructorParameter 510 && ast.getType() == TokenTypes.PARAMETER_DEF) { 511 final DetailAST parametersAST = ast.getParent(); 512 final DetailAST constructorAST = parametersAST.getParent(); 513 result = constructorAST.getType() == TokenTypes.CTOR_DEF; 514 } 515 return result; 516 } 517 518 /** 519 * Decides whether to ignore an AST node that is the parameter of an 520 * abstract method. 521 * 522 * @param ast the AST to check. 523 * @return true if ast should be ignored because check property 524 * ignoreAbstractMethods is true and ast is a parameter of abstract methods. 525 */ 526 private boolean isIgnoredParamOfAbstractMethod(DetailAST ast) { 527 boolean result = false; 528 if (ignoreAbstractMethods) { 529 final DetailAST method = ast.getParent().getParent(); 530 if (method.getType() == TokenTypes.METHOD_DEF) { 531 final DetailAST mods = method.findFirstToken(TokenTypes.MODIFIERS); 532 result = mods.findFirstToken(TokenTypes.ABSTRACT) != null; 533 } 534 } 535 return result; 536 } 537 538 /** 539 * Setter to define the RegExp for names of variables and parameters to ignore. 540 * 541 * @param pattern a pattern. 542 * @since 3.2 543 */ 544 public void setIgnoreFormat(Pattern pattern) { 545 ignoreFormat = pattern; 546 } 547 548 /** 549 * Setter to allow to ignore the parameter of a property setter method. 550 * 551 * @param ignoreSetter decide whether to ignore the parameter of 552 * a property setter method. 553 * @since 3.2 554 */ 555 public void setIgnoreSetter(boolean ignoreSetter) { 556 this.ignoreSetter = ignoreSetter; 557 } 558 559 /** 560 * Setter to allow to expand the definition of a setter method to include methods 561 * that return the class' instance. 562 * 563 * @param aSetterCanReturnItsClass if true then setter can return 564 * either void or class in which it is declared. If false then 565 * in order to be recognized as setter method (otherwise 566 * already recognized as a setter) must return void. Later is 567 * the default behavior. 568 * @since 6.3 569 */ 570 public void setSetterCanReturnItsClass( 571 boolean aSetterCanReturnItsClass) { 572 setterCanReturnItsClass = aSetterCanReturnItsClass; 573 } 574 575 /** 576 * Setter to control whether to ignore constructor parameters. 577 * 578 * @param ignoreConstructorParameter decide whether to ignore 579 * constructor parameters. 580 * @since 3.2 581 */ 582 public void setIgnoreConstructorParameter( 583 boolean ignoreConstructorParameter) { 584 this.ignoreConstructorParameter = ignoreConstructorParameter; 585 } 586 587 /** 588 * Setter to control whether to ignore parameters of abstract methods. 589 * 590 * @param ignoreAbstractMethods decide whether to ignore 591 * parameters of abstract methods. 592 * @since 4.0 593 */ 594 public void setIgnoreAbstractMethods( 595 boolean ignoreAbstractMethods) { 596 this.ignoreAbstractMethods = ignoreAbstractMethods; 597 } 598 599 /** 600 * Holds the names of static and instance fields of a type. 601 */ 602 private static final class FieldFrame { 603 604 /** Name of the frame, such name of the class or enum declaration. */ 605 private final String frameName; 606 607 /** Is this a static inner type. */ 608 private final boolean staticType; 609 610 /** Parent frame. */ 611 private final FieldFrame parent; 612 613 /** Set of instance field names. */ 614 private final Set<String> instanceFields = new HashSet<>(); 615 616 /** Set of static field names. */ 617 private final Set<String> staticFields = new HashSet<>(); 618 619 /** 620 * Creates new frame. 621 * 622 * @param parent parent frame. 623 * @param staticType is this a static inner type (class or enum). 624 * @param frameName name associated with the frame, which can be a 625 */ 626 private FieldFrame(FieldFrame parent, boolean staticType, String frameName) { 627 this.parent = parent; 628 this.staticType = staticType; 629 this.frameName = frameName; 630 } 631 632 /** 633 * Adds an instance field to this FieldFrame. 634 * 635 * @param field the name of the instance field. 636 */ 637 public void addInstanceField(String field) { 638 instanceFields.add(field); 639 } 640 641 /** 642 * Adds a static field to this FieldFrame. 643 * 644 * @param field the name of the instance field. 645 */ 646 public void addStaticField(String field) { 647 staticFields.add(field); 648 } 649 650 /** 651 * Determines whether this FieldFrame contains an instance field. 652 * 653 * @param field the field to check 654 * @return true if this FieldFrame contains instance field 655 */ 656 public boolean containsInstanceField(String field) { 657 FieldFrame currentParent = parent; 658 boolean contains = instanceFields.contains(field); 659 boolean isStaticType = staticType; 660 while (!isStaticType && !contains) { 661 contains = currentParent.instanceFields.contains(field); 662 isStaticType = currentParent.staticType; 663 currentParent = currentParent.parent; 664 } 665 return contains; 666 } 667 668 /** 669 * Determines whether this FieldFrame contains a static field. 670 * 671 * @param field the field to check 672 * @return true if this FieldFrame contains static field 673 */ 674 public boolean containsStaticField(String field) { 675 FieldFrame currentParent = parent; 676 boolean contains = staticFields.contains(field); 677 while (currentParent != null && !contains) { 678 contains = currentParent.staticFields.contains(field); 679 currentParent = currentParent.parent; 680 } 681 return contains; 682 } 683 684 /** 685 * Getter for parent frame. 686 * 687 * @return parent frame. 688 */ 689 public FieldFrame getParent() { 690 return parent; 691 } 692 693 /** 694 * Check if current frame is embedded in class or enum with 695 * specific name. 696 * 697 * @param classOrEnumName name of class or enum that we are looking 698 * for in the chain of field frames. 699 * 700 * @return true if current frame is embedded in class or enum 701 * with name classOrNameName 702 */ 703 private boolean isEmbeddedIn(String classOrEnumName) { 704 FieldFrame currentFrame = this; 705 boolean isEmbeddedIn = false; 706 while (currentFrame != null) { 707 if (Objects.equals(currentFrame.frameName, classOrEnumName)) { 708 isEmbeddedIn = true; 709 break; 710 } 711 currentFrame = currentFrame.parent; 712 } 713 return isEmbeddedIn; 714 } 715 716 } 717 718}