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