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