1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 package com.oracle.javafx.scenebuilder.kit.fxom;
  33 
  34 import com.oracle.javafx.scenebuilder.kit.fxom.glue.GlueElement;
  35 import com.oracle.javafx.scenebuilder.kit.metadata.util.PrefixedValue;
  36 import com.oracle.javafx.scenebuilder.kit.metadata.util.PropertyName;
  37 import com.oracle.javafx.scenebuilder.kit.util.JavaLanguage;
  38 import com.oracle.javafx.scenebuilder.kit.util.URLUtils;
  39 import java.util.ArrayList;
  40 import java.util.HashSet;
  41 import java.util.LinkedHashMap;
  42 import java.util.List;
  43 import java.util.Map;
  44 import java.util.Set;
  45 import javafx.scene.Node;
  46 import javafx.scene.Scene;
  47 
  48 /**
  49  *
  50  *
  51  */
  52 public abstract class FXOMObject extends FXOMNode {
  53 
  54     private final GlueElement glueElement;
  55     private FXOMPropertyC parentProperty;
  56     private FXOMCollection parentCollection;
  57     private Object sceneGraphObject;
  58 
  59     FXOMObject(FXOMDocument fxomDocument, GlueElement glueElement, Object sceneGraphObject) {
  60         super(fxomDocument);
  61 
  62         assert glueElement != null;
  63         assert glueElement.getDocument() == fxomDocument.getGlue();
  64 
  65         this.glueElement = glueElement;
  66         this.sceneGraphObject = sceneGraphObject;
  67     }
  68 
  69     FXOMObject(FXOMDocument fxomDocument, String tagName) {
  70         super(fxomDocument);
  71         this.glueElement = new GlueElement(fxomDocument.getGlue(), tagName);
  72     }
  73 
  74     public GlueElement getGlueElement() {
  75         return glueElement;
  76     }
  77 
  78     public FXOMPropertyC getParentProperty() {
  79         return parentProperty;
  80     }
  81 
  82     public FXOMCollection getParentCollection() {
  83         return parentCollection;
  84     }
  85 
  86     public void addToParentProperty(int index, FXOMPropertyC newParentProperty) {
  87 
  88         assert newParentProperty != null;
  89         assert -1 <= index;
  90         assert index <= newParentProperty.getValues().size();
  91 
  92         if (parentProperty != null) {
  93             if (parentProperty.getValues().size() == 1) {
  94                 // it's the last value -> we remove the whole property
  95                 if (parentProperty.getParentInstance() != null) {
  96                     parentProperty.removeFromParentInstance();
  97                 }
  98             } else {
  99                 removeFromParentProperty();
 100             }
 101         } else if (parentCollection != null) {
 102             removeFromParentCollection();
 103         }
 104 
 105         parentProperty = newParentProperty;
 106         newParentProperty.addValue(index, this);
 107 
 108         final GlueElement newParentElement = parentProperty.getGlueElement();
 109         glueElement.addToParent(index, newParentElement);
 110 
 111         // May be this object was a root : properties like fx:controller must
 112         // be reset to preserve FXML validity.
 113         resetRootProperties();
 114     }
 115 
 116     public void removeFromParentProperty() {
 117         assert parentProperty != null;
 118         assert parentProperty.getParentInstance() == null
 119                 || parentProperty.getValues().size() >= 2;
 120 
 121         assert glueElement.getParent() == parentProperty.getGlueElement();
 122         glueElement.removeFromParent();
 123 
 124         final FXOMPropertyC keepParentProperty = parentProperty;
 125         parentProperty = null;
 126         keepParentProperty.removeValue(this);
 127     }
 128 
 129     public int getIndexInParentProperty() {
 130         final int result;
 131 
 132         if (parentProperty == null) {
 133             result = -1;
 134         } else {
 135             result = parentProperty.getValues().indexOf(this);
 136             assert result != -1;
 137         }
 138 
 139         return result;
 140     }
 141 
 142 
 143     public void addToParentCollection(int index, FXOMCollection newParentCollection) {
 144 
 145         assert newParentCollection != null;
 146         assert -1 <= index;
 147         assert index <= newParentCollection.getItems().size();
 148 
 149         if (parentProperty != null) {
 150             removeFromParentProperty();
 151         } else if (parentCollection != null) {
 152             removeFromParentCollection();
 153         }
 154 
 155         parentCollection = newParentCollection;
 156         newParentCollection.addValue(index, this);
 157 
 158         final GlueElement newParentElement = parentCollection.getGlueElement();
 159         glueElement.addToParent(index, newParentElement);
 160 
 161         // May be this object was a root : properties like fx:controller must
 162         // be reset to preserve FXML validity.
 163         resetRootProperties();
 164     }
 165 
 166     public void removeFromParentCollection() {
 167         assert parentCollection != null;
 168 
 169         assert glueElement.getParent() == parentCollection.getGlueElement();
 170         glueElement.removeFromParent();
 171 
 172         final FXOMCollection keepParentCollection = parentCollection;
 173         parentCollection = null;
 174         keepParentCollection.removeValue(this);
 175     }
 176 
 177     public int getIndexInParentCollection() {
 178         final int result;
 179 
 180         if (parentCollection == null) {
 181             result = -1;
 182         } else {
 183             result = parentCollection.getItems().indexOf(this);
 184             assert result != -1;
 185         }
 186 
 187         return result;
 188     }
 189 
 190     public Object getSceneGraphObject() {
 191         return sceneGraphObject;
 192     }
 193 
 194     public void setSceneGraphObject(Object sceneGraphObject) {
 195         this.sceneGraphObject = sceneGraphObject;
 196     }
 197 
 198     public FXOMObject getNextSlibing() {
 199         final FXOMObject result;
 200 
 201         if (parentProperty != null) {
 202             final int index = getIndexInParentProperty();
 203             assert index != -1;
 204             if (index+1 < parentProperty.getValues().size()) {
 205                 result = parentProperty.getValues().get(index+1);
 206             } else {
 207                 result = null;
 208             }
 209         } else if (parentCollection != null) {
 210             final int index = getIndexInParentCollection();
 211             assert index != -1;
 212             if (index+1 < parentCollection.getItems().size()) {
 213                 result = parentCollection.getItems().get(index+1);
 214             } else {
 215                 result = null;
 216             }
 217         } else {
 218             result = null;
 219         }
 220 
 221         return result;
 222     }
 223 
 224     public FXOMObject getPreviousSlibing() {
 225         final FXOMObject result;
 226 
 227         if (parentProperty != null) {
 228             final int index = getIndexInParentProperty();
 229             assert index != -1;
 230             if (index-1 >= 0) {
 231                 result = parentProperty.getValues().get(index-1);
 232             } else {
 233                 result = null;
 234             }
 235         } else if (parentCollection != null) {
 236             final int index = getIndexInParentCollection();
 237             assert index != -1;
 238             if (index-1 >= 0) {
 239                 result = parentCollection.getItems().get(index-1);
 240             } else {
 241                 result = null;
 242             }
 243         } else {
 244             result = null;
 245         }
 246 
 247         return result;
 248     }
 249 
 250     public void moveBeforeSibling(FXOMObject sibling) {
 251         assert sibling != this;
 252         assert (parentProperty != null) || (parentCollection != null);
 253 
 254         if (parentProperty != null) {
 255             assert (sibling == null) || (sibling.getParentProperty() == parentProperty);
 256 
 257             final FXOMPropertyC oldParentProperty = parentProperty;
 258             removeFromParentProperty();
 259             assert parentProperty == null;
 260 
 261             final int index;
 262             if (sibling == null) {
 263                 index = -1;
 264             } else {
 265                 index = sibling.getIndexInParentProperty();
 266             }
 267             addToParentProperty(index, oldParentProperty);
 268 
 269         } else if (parentCollection != null) {
 270             assert (sibling == null) || (sibling.getParentCollection() == parentCollection);
 271 
 272             final FXOMCollection oldParentCollection = parentCollection;
 273             removeFromParentCollection();
 274             assert parentCollection == null;
 275 
 276             final int index;
 277             if (sibling == null) {
 278                 index = -1;
 279             } else {
 280                 index = sibling.getIndexInParentCollection();
 281             }
 282             addToParentCollection(index, oldParentCollection);
 283         } else {
 284             assert false;
 285         }
 286     }
 287 
 288     public Scene getScene() {
 289         final Scene result;
 290 
 291         if (sceneGraphObject instanceof Node) {
 292             final Node sceneGraphNode = (Node) sceneGraphObject;
 293             result = sceneGraphNode.getScene();
 294         } else  {
 295             result = null;
 296         }
 297 
 298         return result;
 299     }
 300 
 301     public FXOMObject getFirstAncestorWithNonNullScene() {
 302         FXOMObject result = this;
 303 
 304         while ((result != null) && (result.getScene() == null)) {
 305             result = result.getParentObject();
 306         }
 307 
 308         return result;
 309     }
 310 
 311     /*
 312      * Utilities
 313      */
 314 
 315     public FXOMObject searchWithSceneGraphObject(Object sceneGraphObject) {
 316         final FXOMObject result;
 317 
 318         if (this.sceneGraphObject == sceneGraphObject) {
 319             result = this;
 320         } else {
 321             result = null;
 322         }
 323 
 324         return result;
 325     }
 326 
 327     public FXOMObject searchWithFxId(String fxId) {
 328         final FXOMObject result;
 329 
 330         assert fxId != null;
 331 
 332         if (fxId.equals(getFxId())) {
 333             result = this;
 334         } else {
 335             result = null;
 336         }
 337 
 338         return result;
 339     }
 340 
 341     public Set<Class<?>> collectDeclaredClasses() {
 342         final Set<Class<?>> result = new HashSet<>();
 343 
 344         collectDeclaredClasses(result);
 345 
 346         return result;
 347     }
 348 
 349     protected abstract void collectDeclaredClasses(Set<Class<?>> result);
 350 
 351 
 352     public List<FXOMProperty> collectProperties(PropertyName propertyName) {
 353         final List<FXOMProperty> result = new ArrayList<>();
 354 
 355         collectProperties(propertyName, result);
 356 
 357         return result;
 358     }
 359 
 360     protected abstract void collectProperties(PropertyName propertyName, List<FXOMProperty> result);
 361 
 362 
 363     public List<FXOMPropertyT> collectNullProperties() {
 364         final List<FXOMPropertyT> result = new ArrayList<>();
 365 
 366         collectNullProperties(result);
 367 
 368         return result;
 369     }
 370 
 371     protected abstract void collectNullProperties(List<FXOMPropertyT> result);
 372 
 373 
 374     public List<FXOMPropertyT> collectPropertiesT() {
 375         final List<FXOMPropertyT> result = new ArrayList<>();
 376 
 377         collectPropertiesT(result);
 378 
 379         return result;
 380     }
 381 
 382     protected abstract void collectPropertiesT(List<FXOMPropertyT> result);
 383 
 384 
 385     public List<FXOMIntrinsic> collectReferences(String source) {
 386         final List<FXOMIntrinsic> result = new ArrayList<>();
 387 
 388         collectReferences(source, result);
 389 
 390         return result;
 391     }
 392 
 393     protected abstract void collectReferences(String source, List<FXOMIntrinsic> result);
 394 
 395     public List<FXOMNode> collectReferences(String source, FXOMObject scope) {
 396         assert source != null;
 397 
 398         final List<FXOMNode> result = new ArrayList<>();
 399 
 400         collectReferences(source, scope, result);
 401 
 402         return result;
 403     }
 404 
 405     protected abstract void collectReferences(String source, FXOMObject scope, List<FXOMNode> result);
 406 
 407     public List<FXOMIntrinsic> collectIncludes(String source) {
 408         final List<FXOMIntrinsic> result = new ArrayList<>();
 409 
 410         collectIncludes(source, result);
 411 
 412         return result;
 413     }
 414 
 415     protected abstract void collectIncludes(String source, List<FXOMIntrinsic> result);
 416 
 417     public Map<String, FXOMObject> collectFxIds() {
 418         final Map<String, FXOMObject> result = new LinkedHashMap<>();
 419 
 420         collectFxIds(result);
 421 
 422         return result;
 423     }
 424 
 425     protected abstract void collectFxIds(Map<String, FXOMObject> result);
 426 
 427     public List<FXOMObject> collectObjectWithSceneGraphObjectClass(Class<?> sceneGraphObjectClass) {
 428         final List<FXOMObject> result = new ArrayList<>();
 429 
 430         collectObjectWithSceneGraphObjectClass(sceneGraphObjectClass, result);
 431 
 432         return result;
 433     }
 434 
 435     protected abstract void collectObjectWithSceneGraphObjectClass(Class<?> sceneGraphObjectClass, List<FXOMObject> result);
 436 
 437     public List<FXOMPropertyT> collectEventHandlers() {
 438         final List<FXOMPropertyT> result = new ArrayList<>();
 439 
 440         collectEventHandlers(result);
 441 
 442         return result;
 443     }
 444 
 445     protected abstract void collectEventHandlers(List<FXOMPropertyT> result);
 446 
 447     /*
 448      * Utilities
 449      */
 450 
 451     public FXOMObject getParentObject() {
 452         final FXOMObject result;
 453         if (parentProperty != null) {
 454             assert parentCollection == null;
 455             result = parentProperty.getParentInstance();
 456         } else if (parentCollection != null) {
 457             result = parentCollection;
 458         } else {
 459             result = null;
 460         }
 461         return result;
 462     }
 463 
 464     public void removeFromParentObject() {
 465         if (parentProperty != null) {
 466             if (parentProperty.getValues().size() == 1) {
 467                 // This object is the last value of its parent property
 468                 // We remove the property from the parent instance
 469                 if (parentProperty.getParentInstance() != null) {
 470                     parentProperty.removeFromParentInstance();
 471                 }
 472             } else {
 473                 removeFromParentProperty();
 474             }
 475         } else if (parentCollection != null) {
 476             removeFromParentCollection();
 477        }
 478     }
 479 
 480     public abstract List<FXOMObject> getChildObjects();
 481 
 482     public boolean isDescendantOf(FXOMObject other) {
 483         final boolean result;
 484 
 485         if (other == null) {
 486             result = true;
 487         } else {
 488             FXOMObject ancestor = getParentObject();
 489             while ((ancestor != other) && (ancestor != null)) {
 490                 ancestor = ancestor.getParentObject();
 491             }
 492             result = (ancestor != null);
 493         }
 494 
 495         return result;
 496     }
 497 
 498     public boolean isNode() {
 499         return sceneGraphObject instanceof Node;
 500     }
 501 
 502     public FXOMObject getClosestNode() {
 503         FXOMObject result;
 504 
 505         result = this;
 506         while ((result.isNode() == false) && (result.getParentObject() != null)) {
 507             result = result.getParentObject();
 508         }
 509 
 510         return result.isNode() ? result : null;
 511     }
 512 
 513     public String getFxId() {
 514         return glueElement.getAttributes().get("fx:id");
 515     }
 516 
 517 
 518     public void setFxId(String fxId) {
 519         assert (fxId == null) || JavaLanguage.isIdentifier(fxId);
 520         if (fxId == null) {
 521             glueElement.getAttributes().remove("fx:id");
 522         } else {
 523             glueElement.getAttributes().put("fx:id", fxId);
 524         }
 525     }
 526 
 527 
 528     public String getFxValue() {
 529         return glueElement.getAttributes().get("fx:value");
 530     }
 531 
 532     public void setFxValue(String fxValue) {
 533         if (fxValue == null) {
 534             glueElement.getAttributes().remove("fx:value");
 535         } else {
 536             glueElement.getAttributes().put("fx:value", fxValue);
 537         }
 538     }
 539 
 540 
 541     public String getFxConstant() {
 542         return glueElement.getAttributes().get("fx:constant");
 543     }
 544 
 545     public void setFxConstant(String fxConstant) {
 546         if (fxConstant == null) {
 547             glueElement.getAttributes().remove("fx:constant");
 548         } else {
 549             glueElement.getAttributes().put("fx:constant", fxConstant);
 550         }
 551     }
 552 
 553     public String getFxController() {
 554         return glueElement.getAttributes().get("fx:controller");
 555     }
 556 
 557     public void setFxController(String fxController) {
 558         if (fxController == null) {
 559             glueElement.getAttributes().remove("fx:controller");
 560         } else {
 561             glueElement.getAttributes().put("fx:controller", fxController);
 562         }
 563     }
 564 
 565     public String getFxFactory() {
 566         return glueElement.getAttributes().get("fx:factory");
 567     }
 568 
 569     public void setFxFactory(String fxFactory) {
 570         if (fxFactory == null) {
 571             glueElement.getAttributes().remove("fx:factory");
 572         } else {
 573             glueElement.getAttributes().put("fx:factory", fxFactory);
 574         }
 575     }
 576 
 577     public String getNameSpaceFX() {
 578         return glueElement.getAttributes().get("xmlns");
 579     }
 580 
 581     public void setNameSpaceFX(String nameSpace) {
 582         if (nameSpace == null) {
 583             glueElement.getAttributes().remove("xmlns");
 584         } else {
 585             glueElement.getAttributes().put("xmlns", nameSpace);
 586         }
 587     }
 588 
 589     public String getNameSpaceFXML() {
 590         return glueElement.getAttributes().get("xmlns:fx");
 591     }
 592 
 593     public void setNameSpaceFXML(String nameSpace) {
 594         if (nameSpace == null) {
 595             glueElement.getAttributes().remove("xmlns:fx");
 596         } else {
 597             glueElement.getAttributes().put("xmlns:fx", nameSpace);
 598         }
 599     }
 600 
 601     /*
 602      * FXOMNode
 603      */
 604 
 605     @Override
 606     public void moveToFxomDocument(FXOMDocument destination) {
 607         assert destination != null;
 608         assert destination != getFxomDocument();
 609         assert (parentProperty == null)
 610                 || (parentProperty.getParentInstance() == null)
 611                 || (parentProperty.getValues().size() >= 2);
 612 
 613         if (URLUtils.equals(getFxomDocument().getLocation(), destination.getLocation()) == false) {
 614             documentLocationWillChange(destination.getLocation());
 615         }
 616 
 617         final Map<String, FXOMObject> destinationFxIds = destination.collectFxIds();
 618         final Map<String, FXOMObject> importedFxIds = collectFxIds();
 619         final FXOMFxIdMerger merger = new FXOMFxIdMerger(destinationFxIds.keySet(), importedFxIds.keySet());
 620         for (Map.Entry<String, FXOMObject> e : importedFxIds.entrySet()) {
 621             final String originalFxId = e.getKey();
 622             final FXOMObject fxomObject = e.getValue();
 623             assert originalFxId.equals(fxomObject.getFxId());
 624             final String renamedFxId = merger.getRenamedFxId(originalFxId);
 625             assert renamedFxId != null;
 626 
 627             if (renamedFxId.equals(originalFxId) == false) {
 628                 /*
 629                  * Apply the renaming:
 630                  * 1) the declaration
 631                  *      <Button fx:id="toto" .../>
 632                  * 2) expressions that reference the fx:id
 633                  *      ... labelFor="$toto" ...
 634                  * 3) intrinsics that reference the fx:id
 635                  *      <fx:reference source="toto"/>
 636                  */
 637 
 638                 // #1
 639                 fxomObject.setFxId(renamedFxId);
 640                 // #2
 641                 final PrefixedValue pv = new PrefixedValue(PrefixedValue.Type.EXPRESSION, renamedFxId);
 642                 final String newValue = pv.toString();
 643                 for (FXOMPropertyT p : FXOMNodes.collectReferenceExpression(this, originalFxId)) {
 644                     p.setValue(newValue);
 645                 }
 646                 // #3
 647                 for (FXOMObject o : FXOMNodes.serializeObjects(this)) {
 648                     if (o instanceof FXOMIntrinsic) {
 649                         final FXOMIntrinsic i = (FXOMIntrinsic) o;
 650                         switch(i.getType()) {
 651                             case FX_REFERENCE:
 652                             case FX_COPY:
 653                                 if (i.getSource().equals(originalFxId)) {
 654                                     i.setSource(renamedFxId);
 655                                 }
 656                                 break;
 657                             default:
 658                                 // "source" does not contain an fx:id
 659                                 break;
 660                         }
 661                     }
 662                 }
 663             }
 664         }
 665 
 666         if (parentProperty != null) {
 667             assert parentProperty.getFxomDocument() == getFxomDocument();
 668             removeFromParentProperty();
 669         } else if (parentCollection != null) {
 670             assert parentCollection.getFxomDocument() == getFxomDocument();
 671             removeFromParentCollection();
 672         } else if (getFxomDocument().getFxomRoot() == this) {
 673             getFxomDocument().setFxomRoot(null);
 674         }
 675 
 676         assert parentProperty == null;
 677         assert parentCollection == null;
 678         assert glueElement.getParent() == null;
 679 
 680         glueElement.moveToDocument(destination.getGlue());
 681         changeFxomDocument(destination);
 682     }
 683 
 684     @Override
 685     protected void changeFxomDocument(FXOMDocument destination) {
 686         assert destination != null;
 687         assert destination != getFxomDocument();
 688         assert destination.getGlue() == glueElement.getDocument();
 689         assert (parentProperty   == null) || (destination == parentProperty.getFxomDocument());
 690         assert (parentCollection == null) || (destination == parentCollection.getFxomDocument());
 691 
 692         super.changeFxomDocument(destination);
 693     }
 694 
 695 
 696     /*
 697      * Object
 698      */
 699 
 700     @Override
 701     public String toString() {
 702         final StringBuilder result = new StringBuilder();
 703 
 704         result.append(getClass().getSimpleName());
 705         result.append("[tagName=");
 706         result.append(glueElement.getTagName());
 707         if (getFxId() != null) {
 708             result.append(",fx:id=");
 709             result.append(getFxId());
 710         }
 711         result.append(']');
 712 
 713         return result.toString();
 714     }
 715 
 716     /*
 717      * Package
 718      */
 719 
 720     /* For FXOMPropertyC constructor private use */
 721     void setParentProperty(FXOMPropertyC newParentProperty) {
 722         assert parentProperty == null;
 723         assert parentCollection == null;
 724         assert newParentProperty.getValues().contains(this);
 725         parentProperty = newParentProperty;
 726     }
 727 
 728     /* For FXOMCollection constructor private use */
 729     void setParentCollection(FXOMCollection newParentCollection) {
 730         assert parentProperty == null;
 731         assert parentCollection == null;
 732         assert newParentCollection.getItems().contains(this);
 733         parentCollection = newParentCollection;
 734     }
 735 
 736 
 737 
 738     /*
 739      * Private
 740      */
 741 
 742     private void resetRootProperties() {
 743         setFxController(null);
 744         setNameSpaceFX(null);
 745         setNameSpaceFXML(null);
 746     }
 747 }