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