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