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