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