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