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