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