001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.coding;
021
022import java.util.HashSet;
023import java.util.Locale;
024import java.util.Objects;
025import java.util.Set;
026import java.util.regex.Pattern;
027
028import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
029import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
030import com.puppycrawl.tools.checkstyle.api.DetailAST;
031import com.puppycrawl.tools.checkstyle.api.Scope;
032import com.puppycrawl.tools.checkstyle.api.TokenTypes;
033import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
034import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
035import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
036
037/**
038 * <div>
039 * Checks that a local variable or a parameter does not shadow
040 * a field that is defined in the same class.
041 * </div>
042 *
043 * <p>
044 * Notes:
045 * It is possible to configure the check to ignore all property setter methods.
046 * </p>
047 *
048 * <p>
049 * A method is recognized as a setter if it is in the following form
050 * </p>
051 * <div class="wrapper"><pre class="prettyprint"><code class="language-text">
052 * ${returnType} set${Name}(${anyType} ${name}) { ... }
053 * </code></pre></div>
054 *
055 * <p>
056 * where ${anyType} is any primitive type, class or interface name;
057 * ${name} is name of the variable that is being set and ${Name} its
058 * capitalized form that appears in the method name. By default, it is expected
059 * that setter returns void, i.e. ${returnType} is 'void'. For example
060 * </p>
061 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
062 * void setTime(long time) { ... }
063 * </code></pre></div>
064 *
065 * <p>
066 * Any other return types will not let method match a setter pattern. However,
067 * by setting <em>setterCanReturnItsClass</em> property to <em>true</em>
068 * definition of a setter is expanded, so that setter return type can also be
069 * a class in which setter is declared. For example
070 * </p>
071 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
072 * class PageBuilder {
073 *   PageBuilder setName(String name) { ... }
074 * }
075 * </code></pre></div>
076 *
077 * <p>
078 * Such methods are known as chain-setters and a common when Builder-pattern
079 * is used. Property <em>setterCanReturnItsClass</em> has effect only if
080 * <em>ignoreSetter</em> is set to true.
081 * </p>
082 * <ul>
083 * <li>
084 * Property {@code ignoreAbstractMethods} - Control whether to ignore parameters
085 * of abstract methods.
086 * Type is {@code boolean}.
087 * Default value is {@code false}.
088 * </li>
089 * <li>
090 * Property {@code ignoreConstructorParameter} - Control whether to ignore constructor parameters.
091 * Type is {@code boolean}.
092 * Default value is {@code false}.
093 * </li>
094 * <li>
095 * Property {@code ignoreFormat} - Define the RegExp for names of variables
096 * and parameters to ignore.
097 * Type is {@code java.util.regex.Pattern}.
098 * Default value is {@code null}.
099 * </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
145public 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}