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