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