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.GlueDocument;
  35 import com.oracle.javafx.scenebuilder.kit.fxom.glue.GlueInstruction;
  36 import com.oracle.javafx.scenebuilder.kit.metadata.Metadata;
  37 import com.oracle.javafx.scenebuilder.kit.metadata.klass.ComponentClassMetadata;
  38 import com.oracle.javafx.scenebuilder.kit.metadata.property.PropertyMetadata;
  39 import com.oracle.javafx.scenebuilder.kit.metadata.property.value.DoublePropertyMetadata;
  40 import com.oracle.javafx.scenebuilder.kit.metadata.property.value.ImagePropertyMetadata;
  41 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignImage;
  42 import com.oracle.javafx.scenebuilder.kit.metadata.util.PrefixedValue;
  43 import com.oracle.javafx.scenebuilder.kit.metadata.util.PropertyName;
  44 import com.oracle.javafx.scenebuilder.kit.util.JavaLanguage;
  45 import java.io.File;
  46 import java.io.IOException;
  47 import java.net.URL;
  48 import java.util.ArrayList;
  49 import java.util.Collections;
  50 import java.util.HashSet;
  51 import java.util.List;
  52 import java.util.Map;
  53 import java.util.Set;
  54 import javafx.fxml.FXMLLoader;
  55 import javafx.scene.control.ToggleGroup;
  56 import javafx.scene.image.Image;
  57 import javafx.scene.image.ImageView;
  58 import javafx.scene.media.Media;
  59 import javafx.scene.media.MediaException;
  60 import javafx.scene.media.MediaPlayer;
  61 import javafx.scene.media.MediaView;
  62 
  63 /**
  64  * This class groups static utility methods which operate on FXOMNode and
  65  * subclasses (a bit like Collection and Collections).
  66  *
  67  *
  68  */
  69 public class FXOMNodes {
  70 
  71 
  72     /**
  73      * Sorts the specified set of objects according their location in
  74      * the fxom document. Objets are sorted according depth first order.
  75      * In particular, if objects all have the same parent, then the resulting
  76      * list will be sorted by indexes.
  77      *
  78      * @param objects a set of fxom objects (never null but possibly empty)
  79      * @return the list of objets sorted by position in the fxom document
  80      */
  81     public static List<FXOMObject> sort(Set<FXOMObject> objects) {
  82         final List<FXOMObject> result;
  83 
  84         assert objects != null;
  85 
  86         if (objects.isEmpty()) {
  87             result = Collections.emptyList();
  88         } else if (objects.size() == 1) {
  89             result = Collections.singletonList(objects.iterator().next());
  90         } else {
  91             final FXOMObject object0 = objects.iterator().next();
  92             final FXOMDocument fxomDocument = object0.getFxomDocument();
  93             assert fxomDocument != null;
  94             result = new ArrayList<>();
  95             sort(fxomDocument.getFxomRoot(), objects, result);
  96         }
  97 
  98         return result;
  99     }
 100 
 101 
 102     /**
 103      * Flattens a set of fxom objects.
 104      * A set of fxom objects is declared "flat" if each object member
 105      * of the set has no ancestor member of the set.
 106      *
 107      * @param objects a set of fxom objects (never null)
 108      * @return the flat set of objects.
 109      */
 110     public static Set<FXOMObject> flatten(Set<FXOMObject> objects) {
 111         final Set<FXOMObject> result = new HashSet<>();
 112 
 113         assert objects != null;
 114 
 115         for (FXOMObject o : objects) {
 116             if (lookupAncestor(o, objects) == null) {
 117                 result.add(o);
 118             }
 119         }
 120 
 121         return result;
 122     }
 123 
 124 
 125     /**
 126      * Returns null or the first ancestor of "obj" which belongs to "candidates".
 127      * @param obj an fxom object (never null)
 128      * @param candidates a set of fxom object (not null and not empty)
 129      * @return null or the first ancestor of "obj" which belongs to "candidates".
 130      */
 131     public static FXOMObject lookupAncestor(FXOMObject obj, Set<FXOMObject> candidates) {
 132         assert obj != null;
 133         assert candidates != null;
 134         assert candidates.isEmpty() == false;
 135 
 136         FXOMObject result = obj.getParentObject();
 137         while ((result != null) && (candidates.contains(result) == false)) {
 138             result = result.getParentObject();
 139         }
 140 
 141         return result;
 142     }
 143 
 144 
 145 
 146     public static FXOMObject newObject(FXOMDocument targetDocument, File file)
 147             throws IOException {
 148         assert targetDocument != null;
 149         assert file != null;
 150         FXOMObject result = null;
 151         if (file.getAbsolutePath().endsWith(".fxml")) { //NOI18N
 152             final String fxmlText
 153                     = FXOMDocument.readContentFromURL(file.toURI().toURL());
 154             final FXOMDocument transientDoc = new FXOMDocument(
 155                     fxmlText,
 156                     targetDocument.getLocation(),
 157                     targetDocument.getClassLoader(),
 158                     targetDocument.getResources());
 159             result = transientDoc.getFxomRoot();
 160             if (result != null) {
 161                 result.moveToFxomDocument(targetDocument);
 162             }
 163         } else {
 164             // Try load the file as an image
 165             final String fileURL = file.toURI().toURL().toString();
 166             final Image image = new Image(fileURL);
 167             if (image.isError() == false) {
 168                 final FXOMDocument transientDoc
 169                         = makeFxomDocumentFromImageURL(image, 200.0);
 170                 result = transientDoc.getFxomRoot();
 171                 if (result != null) {
 172                     result.moveToFxomDocument(targetDocument);
 173                 }
 174             } else {
 175                 try {
 176                     final Media media = new Media(fileURL);
 177                     if (media.getError() == null) {
 178                         final FXOMDocument transientDoc
 179                                 = makeFxomDocumentFromMedia(media, 200.0);
 180                         result = transientDoc.getFxomRoot();
 181                         if (result != null) {
 182                             result.moveToFxomDocument(targetDocument);
 183                         }
 184                     } else {
 185                         throw new IOException(media.getError());
 186                     }
 187                 } catch(MediaException x) {
 188                     throw new IOException(x);
 189                 }
 190             }
 191         }
 192 
 193         return result;
 194     }
 195 
 196     public static FXOMIntrinsic newInclude(FXOMDocument targetDocument, File file)
 197             throws IOException {
 198         assert targetDocument != null;
 199         assert targetDocument.getLocation() != null;
 200         assert file != null;
 201         FXOMIntrinsic result = null;
 202         if (file.getAbsolutePath().endsWith(".fxml")) { //NOI18N
 203             final URL fxmlURL = file.toURI().toURL();
 204             final String fxmlText = FXOMDocument.readContentFromURL(fxmlURL);
 205             final FXOMDocument transientDoc = new FXOMDocument(
 206                     fxmlText,
 207                     fxmlURL,
 208                     targetDocument.getClassLoader(),
 209                     targetDocument.getResources());
 210             if (transientDoc.getFxomRoot() != null) {
 211                 final PrefixedValue pv
 212                         = PrefixedValue.makePrefixedValue(fxmlURL, targetDocument.getLocation());
 213                 assert pv.isDocumentRelativePath();
 214                 assert pv.toString().startsWith(FXMLLoader.RELATIVE_PATH_PREFIX);
 215                 final String includeRef
 216                         = pv.toString().substring(FXMLLoader.RELATIVE_PATH_PREFIX.length());
 217                 result = new FXOMIntrinsic(targetDocument, FXOMIntrinsic.Type.FX_INCLUDE, includeRef);
 218                 result.setSourceSceneGraphObject(transientDoc.getFxomRoot().getSceneGraphObject());
 219             }
 220         }
 221 
 222         return result;
 223     }
 224 
 225     public static FXOMDocument newDocument(FXOMObject source) {
 226         assert source != null;
 227 
 228         final FXOMDocument result = new FXOMDocument();
 229 
 230         /*
 231          * If source's document contains unresolved objects,
 232          * then clones import instructions from the source document
 233          * to the new document.
 234          */
 235         final FXOMDocument sourceDocument
 236                 = source.getFxomDocument();
 237         assert sourceDocument.getFxomRoot() != null; // contains at least source
 238         final List<FXOMObject> unresolvedObjects
 239                 = collectUnresolvedObjects(sourceDocument.getFxomRoot());
 240         if (unresolvedObjects.isEmpty() == false) {
 241             // Copy all the imports from the source document to the new document
 242             final GlueDocument sourceGlue = sourceDocument.getGlue();
 243             final GlueDocument resultGlue = result.getGlue();
 244             for (GlueInstruction i : sourceGlue.collectInstructions("import")) {
 245                 final GlueInstruction ci = new GlueInstruction(resultGlue, i.getTarget(), i.getData());
 246                 resultGlue.getHeader().add(ci);
 247             }
 248         }
 249 
 250         /*
 251          * Clones source to the new document
 252          */
 253         final FXOMCloner cloner = new FXOMCloner(result);
 254         final FXOMObject sourceClone = cloner.clone(source);
 255 
 256         /*
 257          * Setup new document : sourceClone is the root,
 258          * same location, same class loader.
 259          */
 260         result.beginUpdate();
 261         result.setLocation(sourceDocument.getLocation());
 262         result.setClassLoader(sourceDocument.getClassLoader());
 263         result.setFxomRoot(sourceClone);
 264         if (result.getFxomRoot() instanceof FXOMInstance) {
 265             trimStaticProperties((FXOMInstance) result.getFxomRoot());
 266         }
 267         result.endUpdate();
 268 
 269         return result;
 270     }
 271 
 272 
 273     public static void updateProperty(FXOMInstance fxomInstance, FXOMProperty sourceProperty) {
 274         assert fxomInstance != null;
 275         assert sourceProperty != null;
 276         assert sourceProperty.getFxomDocument() == fxomInstance.getFxomDocument();
 277 
 278         final FXOMProperty currentProperty = fxomInstance.getProperties().get(sourceProperty.getName());
 279         if (currentProperty == null) {
 280             sourceProperty.addToParentInstance(-1, fxomInstance);
 281         } else if ((currentProperty instanceof FXOMPropertyT)
 282                 && (sourceProperty instanceof FXOMPropertyT)) {
 283             final FXOMPropertyT currentPropertyT = (FXOMPropertyT) currentProperty;
 284             final FXOMPropertyT newPropertyT = (FXOMPropertyT) sourceProperty;
 285             updateProperty(currentPropertyT, newPropertyT);
 286         } else if ((currentProperty instanceof FXOMPropertyC)
 287                 && (sourceProperty instanceof FXOMPropertyC)) {
 288             final FXOMPropertyC currentPropertyC = (FXOMPropertyC) currentProperty;
 289             final FXOMPropertyC newPropertyC = (FXOMPropertyC) sourceProperty;
 290             updateProperty(currentPropertyC, newPropertyC);
 291         } else {
 292             final int index = currentProperty.getIndexInParentInstance();
 293             currentProperty.removeFromParentInstance();
 294             sourceProperty.addToParentInstance(index, fxomInstance);
 295         }
 296     }
 297 
 298 
 299     public static void updateProperty(FXOMPropertyT fxomProperty, FXOMPropertyT sourceProperty) {
 300         assert fxomProperty != null;
 301         assert sourceProperty != null;
 302         assert fxomProperty.getName().equals(sourceProperty.getName());
 303         fxomProperty.setValue(sourceProperty.getValue());
 304     }
 305 
 306 
 307     public static void updateProperty(FXOMPropertyC fxomProperty, FXOMPropertyC sourceProperty) {
 308         assert fxomProperty != null;
 309         assert sourceProperty != null;
 310         assert fxomProperty.getName().equals(sourceProperty.getName());
 311 
 312         final List<FXOMObject> currentValues = new ArrayList<>();
 313         currentValues.addAll(fxomProperty.getValues());
 314         final List<FXOMObject> sourceValues = new ArrayList<>();
 315         sourceValues.addAll(sourceProperty.getValues());
 316 
 317         final int currentCount = currentValues.size();
 318         final int newCount = sourceValues.size();
 319         final int updateCount = Math.min(currentCount, newCount);
 320 
 321         // Update items
 322         for (int i = 0; i < updateCount; i++) {
 323             final FXOMObject currentValue = currentValues.get(i);
 324             final FXOMObject newValue = sourceValues.get(i);
 325             if ((currentValue instanceof FXOMInstance) &&
 326                     (newValue instanceof FXOMInstance)) {
 327                 final FXOMInstance currentInstance = (FXOMInstance) currentValue;
 328                 final FXOMInstance newInstance = (FXOMInstance) newValue;
 329                 if (currentInstance.getDeclaredClass() == newInstance.getDeclaredClass()) {
 330                     updateInstance(currentInstance, newInstance);
 331                 } else {
 332                     replacePropertyValue(currentValue, newValue);
 333                 }
 334             } else if ((currentValue instanceof FXOMCollection) &&
 335                     (newValue instanceof FXOMCollection)) {
 336                 final FXOMCollection currentCollection = (FXOMCollection) currentValue;
 337                 final FXOMCollection newCollection = (FXOMCollection) newValue;
 338                 updateCollection(currentCollection, newCollection);
 339             } else if ((currentValue instanceof FXOMIntrinsic) &&
 340                     (newValue instanceof FXOMIntrinsic)) {
 341                 final FXOMIntrinsic currentIntrinsic = (FXOMIntrinsic) currentValue;
 342                 final FXOMIntrinsic newIntrinsic = (FXOMIntrinsic) newValue;
 343                 updateIntrinsic(currentIntrinsic, newIntrinsic);
 344             } else {
 345                 replacePropertyValue(currentValue, newValue);
 346            }
 347         }
 348 
 349         if (currentCount < newCount) {
 350             // Add new items
 351             for (int i = currentCount; i < newCount; i++) {
 352                 final FXOMObject newValue = sourceValues.get(i);
 353                 newValue.addToParentProperty(-1, fxomProperty);
 354             }
 355         } else {
 356             // Delete old items
 357             for (int i = newCount; i < currentCount; i++) {
 358                 final FXOMObject currentValue = currentValues.get(i);
 359                 currentValue.removeFromParentProperty();
 360             }
 361         }
 362     }
 363 
 364 
 365     public static void updateInstance(FXOMInstance fxomInstance, FXOMInstance sourceInstance) {
 366         assert fxomInstance != null;
 367         assert sourceInstance != null;
 368         assert fxomInstance.getFxomDocument() == sourceInstance.getFxomDocument();
 369         assert fxomInstance.getDeclaredClass() == sourceInstance.getDeclaredClass();
 370 
 371         // Compute obsolete properties.
 372         // It must be done here because sourceInstance is going to mutate.
 373         final Set<PropertyName> obsoleteNames = new HashSet<>();
 374         obsoleteNames.addAll(fxomInstance.getProperties().keySet());
 375         obsoleteNames.removeAll(sourceInstance.getProperties().keySet());
 376 
 377         // Update properties
 378         final Set<FXOMProperty> sourceProperties = new HashSet<>(sourceInstance.getProperties().values());
 379         for (FXOMProperty sourceProperty : sourceProperties) {
 380             updateProperty(fxomInstance, sourceProperty);
 381         }
 382         // Remove obsolete properties
 383         for (PropertyName pn : obsoleteNames) {
 384             final FXOMProperty fxomProperty = fxomInstance.getProperties().get(pn);
 385             assert fxomProperty != null;
 386             assert fxomProperty.getParentInstance() == fxomInstance;
 387             fxomProperty.removeFromParentInstance();
 388         }
 389 
 390         fxomInstance.setFxConstant(sourceInstance.getFxConstant());
 391         fxomInstance.setFxValue(sourceInstance.getFxValue());
 392         fxomInstance.setFxFactory(sourceInstance.getFxFactory());
 393     }
 394 
 395 
 396     public static void updateCollection(FXOMCollection fxomCollection, FXOMCollection sourceCollection) {
 397         assert fxomCollection != null;
 398         assert sourceCollection != null;
 399         assert fxomCollection.getFxomDocument() == sourceCollection.getFxomDocument();
 400 
 401         final int currentCount = fxomCollection.getItems().size();
 402         final int sourceCount = sourceCollection.getItems().size();
 403         final int updateCount = Math.min(currentCount, sourceCount);
 404 
 405         // Update items
 406         for (int i = 0; i < updateCount; i++) {
 407             final FXOMObject currentValue = fxomCollection.getItems().get(i);
 408             final FXOMObject newValue = sourceCollection.getItems().get(i);
 409             if ((currentValue instanceof FXOMInstance) &&
 410                     (newValue instanceof FXOMInstance)) {
 411                 final FXOMInstance currentInstance = (FXOMInstance) currentValue;
 412                 final FXOMInstance newInstance = (FXOMInstance) newValue;
 413                 updateInstance(currentInstance, newInstance);
 414             } else if ((currentValue instanceof FXOMCollection) &&
 415                     (newValue instanceof FXOMCollection)) {
 416                 final FXOMCollection currentCollection = (FXOMCollection) currentValue;
 417                 final FXOMCollection newCollection = (FXOMCollection) newValue;
 418                 updateCollection(currentCollection, newCollection);
 419             } else if ((currentValue instanceof FXOMIntrinsic) &&
 420                     (newValue instanceof FXOMIntrinsic)) {
 421                 final FXOMIntrinsic currentIntrinsic = (FXOMIntrinsic) currentValue;
 422                 final FXOMIntrinsic newIntrinsic = (FXOMIntrinsic) newValue;
 423                 updateIntrinsic(currentIntrinsic, newIntrinsic);
 424             } else {
 425                 final int index = currentValue.getIndexInParentProperty();
 426                 assert index != -1;
 427                 currentValue.removeFromParentCollection();
 428                 newValue.addToParentCollection(index, fxomCollection);
 429             }
 430         }
 431 
 432         if (currentCount < sourceCount) {
 433             // Add new items
 434             final int addCount = sourceCount - currentCount;
 435             for (int i = 0; i < addCount; i++) {
 436                 final FXOMObject newValue = sourceCollection.getItems().get(i);
 437                 newValue.addToParentCollection(-1, fxomCollection);
 438             }
 439         } else {
 440             // Delete old items
 441             final int removeCount = currentCount - sourceCount;
 442             for (int i = 0; i < removeCount; i++) {
 443                 final FXOMObject currentValue = fxomCollection.getItems().get(sourceCount);
 444                 currentValue.removeFromParentProperty();
 445             }
 446         }
 447 
 448         fxomCollection.setFxConstant(sourceCollection.getFxConstant());
 449         fxomCollection.setFxValue(sourceCollection.getFxValue());
 450         fxomCollection.setFxFactory(sourceCollection.getFxFactory());
 451     }
 452 
 453 
 454     public static void updateIntrinsic(FXOMIntrinsic fxomIntrinsic, FXOMIntrinsic sourceIntrinsic) {
 455         assert fxomIntrinsic != null;
 456         assert sourceIntrinsic != null;
 457         assert fxomIntrinsic.getFxomDocument() != sourceIntrinsic.getFxomDocument();
 458         assert fxomIntrinsic.getType() == sourceIntrinsic.getType();
 459 
 460         fxomIntrinsic.setSource(sourceIntrinsic.getSource());
 461         fxomIntrinsic.setFxConstant(sourceIntrinsic.getFxConstant());
 462         fxomIntrinsic.setFxValue(sourceIntrinsic.getFxValue());
 463         fxomIntrinsic.setFxFactory(sourceIntrinsic.getFxFactory());
 464     }
 465 
 466 
 467     public static List<FXOMPropertyT> collectReferenceExpression(FXOMObject fxomRoot, String fxId) {
 468         assert fxomRoot != null;
 469         assert fxId != null;
 470 
 471         final List<FXOMPropertyT> result = new ArrayList<>();
 472 
 473         for (FXOMPropertyT p : fxomRoot.collectPropertiesT()) {
 474             final PrefixedValue pv = new PrefixedValue(p.getValue());
 475             if (pv.isExpression()) {
 476                 /*
 477                  * p is an FXOMPropertyT like this:
 478                  *
 479                  * <.... property="$id" .... />
 480                  */
 481                 final String id = pv.getSuffix();
 482                 if (id.equals(fxId)) {
 483                     result.add(p);
 484                 }
 485             }
 486         }
 487 
 488         return result;
 489     }
 490 
 491 
 492     public static List<FXOMObject> collectUnresolvedObjects(FXOMObject fxomObject) {
 493         final List<FXOMObject> result = new ArrayList<>();
 494 
 495         for (FXOMObject o : serializeObjects(fxomObject)) {
 496             if (o.getSceneGraphObject() == null) {
 497                 result.add(o);
 498             }
 499         }
 500 
 501         return result;
 502     }
 503 
 504 
 505     public static List<FXOMObject> serializeObjects(FXOMObject fxomObject) {
 506         final List<FXOMObject> result = new ArrayList<>();
 507 
 508         serializeObjects(fxomObject, result);
 509         assert result.isEmpty() == false;
 510         assert result.get(0) == fxomObject;
 511 
 512         return result;
 513     }
 514 
 515     public static void removeToggleGroups(Map<String, FXOMObject> fxIdMap) {
 516         assert fxIdMap != null;
 517 
 518         for (String fxId : new HashSet<>(fxIdMap.keySet())) {
 519             final FXOMObject fxomObject = fxIdMap.get(fxId);
 520             if (fxomObject.getSceneGraphObject() instanceof ToggleGroup) {
 521                 fxIdMap.remove(fxId);
 522             }
 523         }
 524     }
 525 
 526 
 527     public static String extractReferenceSource(FXOMNode node) {
 528         final String result;
 529 
 530         if (node instanceof FXOMIntrinsic) {
 531             final FXOMIntrinsic intrinsic = (FXOMIntrinsic) node;
 532             switch(intrinsic.getType()) {
 533                 case FX_REFERENCE:
 534                 case FX_COPY:
 535                     result = intrinsic.getSource();
 536                     break;
 537                 default:
 538                     result = null;
 539             }
 540         } else if (node instanceof FXOMPropertyT) {
 541             final FXOMPropertyT property = (FXOMPropertyT) node;
 542             final PrefixedValue pv = new PrefixedValue(property.getValue());
 543             if (pv.isExpression() && JavaLanguage.isIdentifier(pv.getSuffix())) {
 544                 result = pv.getSuffix();
 545             } else {
 546                 result = null;
 547             }
 548         } else {
 549             result = null;
 550         }
 551 
 552         return result;
 553     }
 554 
 555 
 556     private static final PropertyName toggleGroupName = new PropertyName("toggleGroup");
 557 
 558     public static boolean isToggleGroupReference(FXOMNode node) {
 559         final boolean result;
 560 
 561         if (extractReferenceSource(node) == null) {
 562             result = false;
 563         } else {
 564             if (node instanceof FXOMIntrinsic) {
 565                 final FXOMIntrinsic intrinsic = (FXOMIntrinsic) node;
 566                 final FXOMProperty parentProperty = intrinsic.getParentProperty();
 567                 if (parentProperty == null) {
 568                     result = false;
 569                 } else {
 570                     result = parentProperty.getName().equals(toggleGroupName);
 571                 }
 572             } else if (node instanceof FXOMPropertyT) {
 573                 final FXOMPropertyT property = (FXOMPropertyT) node;
 574                 result = property.getName().equals(toggleGroupName);
 575             } else {
 576                 result = false;
 577             }
 578         }
 579 
 580         return result;
 581     }
 582 
 583 
 584     public static FXOMPropertyC makeToggleGroup(FXOMDocument fxomDocument, String fxId) {
 585         final FXOMInstance toggleGroup = new FXOMInstance(fxomDocument, ToggleGroup.class);
 586         toggleGroup.setFxId(fxId);
 587         return new FXOMPropertyC(fxomDocument, toggleGroupName, toggleGroup);
 588     }
 589 
 590 
 591     public static boolean isWeakReference(FXOMNode node) {
 592         final boolean result;
 593 
 594         if (node instanceof FXOMIntrinsic) {
 595             final FXOMIntrinsic intrinsic = (FXOMIntrinsic) node;
 596             switch(intrinsic.getType()) {
 597                 case FX_REFERENCE:
 598                 case FX_COPY:
 599                     if (intrinsic.getParentProperty() != null) {
 600                         final PropertyName propertyName = intrinsic.getParentProperty().getName();
 601                         if (propertyName.getResidenceClass() == null) {
 602                             result = getWeakPropertyNames().contains(propertyName.getName());
 603                         } else {
 604                             result = false;
 605                         }
 606                     } else {
 607                         result = false;
 608                     }
 609                     break;
 610                 default:
 611                     result = false;
 612             }
 613         } else if (node instanceof FXOMPropertyT) {
 614             final FXOMPropertyT property = (FXOMPropertyT) node;
 615             final PrefixedValue pv = new PrefixedValue(property.getValue());
 616             if (pv.isExpression() && JavaLanguage.isIdentifier(pv.getSuffix())) {
 617                 final PropertyName propertyName = property.getName();
 618                 if (propertyName.getResidenceClass() == null) {
 619                     result = getWeakPropertyNames().contains(propertyName.getName());
 620                 } else {
 621                     result = false;
 622                 }
 623             } else {
 624                 result = false;
 625             }
 626         } else {
 627             result = false;
 628         }
 629 
 630         return result;
 631     }
 632 
 633 
 634     private static Set<String> weakPropertyNames;
 635 
 636     public static synchronized Set<String> getWeakPropertyNames() {
 637 
 638         if (weakPropertyNames == null) {
 639             weakPropertyNames = new HashSet<>();
 640             weakPropertyNames.add("labelFor");
 641             weakPropertyNames.add("expandedPane");
 642             weakPropertyNames.add("clip");
 643         }
 644 
 645         return weakPropertyNames;
 646     }
 647 
 648 
 649     /*
 650      * Private
 651      */
 652 
 653     private static void sort(FXOMObject from,
 654             Set<FXOMObject> objects, List<FXOMObject> result) {
 655 
 656         if (objects.contains(from)) {
 657             result.add(from);
 658         }
 659 
 660         if (from instanceof FXOMCollection) {
 661             final FXOMCollection collection = (FXOMCollection) from;
 662             for (FXOMObject item : collection.getItems()) {
 663                 sort(item, objects, result);
 664             }
 665         } else if (from instanceof FXOMInstance) {
 666             final FXOMInstance instance = (FXOMInstance) from;
 667             final List<PropertyName> propertyNames
 668                     = new ArrayList<>(instance.getProperties().keySet());
 669             Collections.sort(propertyNames);
 670             for (PropertyName name : propertyNames) {
 671                 final FXOMProperty property = instance.getProperties().get(name);
 672                 assert property != null;
 673                 if (property instanceof FXOMPropertyC) {
 674                     final FXOMPropertyC propertyC = (FXOMPropertyC) property;
 675                     for (FXOMObject v : propertyC.getValues()) {
 676                         sort(v, objects, result);
 677                     }
 678                 }
 679             }
 680         } else {
 681             assert from instanceof FXOMIntrinsic
 682                     : "Unexpected FXOMObject subclass " + from.getClass();
 683         }
 684     }
 685 
 686 
 687     private static void trimStaticProperties(FXOMInstance fxomInstance) {
 688         final List<FXOMProperty> properties =
 689                 new ArrayList<>(fxomInstance.getProperties().values());
 690         for (FXOMProperty p : properties) {
 691             if (p.getName().getResidenceClass() != null) {
 692                 // This is a static property : we remove it.
 693                 p.removeFromParentInstance();
 694             }
 695         }
 696     }
 697 
 698 
 699     private static void replacePropertyValue(FXOMObject replacee, FXOMObject replacement) {
 700         assert replacee.getIndexInParentProperty() != -1;
 701 
 702         final int replaceeIndex = replacee.getIndexInParentProperty();
 703         assert replaceeIndex != -1;
 704         replacement.addToParentProperty(replaceeIndex, replacee.getParentProperty());
 705         replacee.removeFromParentProperty();
 706     }
 707 
 708     private static FXOMDocument makeFxomDocumentFromImageURL(
 709             Image image, double fitSize) throws IOException {
 710 
 711         assert image != null;
 712         assert fitSize > 0.0;
 713 
 714         final double imageWidth = image.getWidth();
 715         final double imageHeight = image.getHeight();
 716 
 717         final double fitWidth, fitHeight;
 718         final double imageSize = Math.max(imageWidth, imageHeight);
 719         if (imageSize < fitSize) {
 720             fitWidth = 0;
 721             fitHeight = 0;
 722         } else {
 723             final double widthScale  = fitSize / imageSize;
 724             final double heightScale = fitSize / imageHeight;
 725             final double scale = Math.min(widthScale, heightScale);
 726             fitWidth = Math.floor(imageWidth * scale);
 727             fitHeight = Math.floor(imageHeight * scale);
 728         }
 729 
 730         return makeFxomDocumentFromImageURL(image, fitWidth, fitHeight);
 731     }
 732 
 733     private static FXOMDocument makeFxomDocumentFromImageURL(
 734             Image image, double fitWidth, double fitHeight) {
 735 
 736         final FXOMDocument result = new FXOMDocument();
 737         final FXOMInstance imageView = new FXOMInstance(result, ImageView.class);
 738 
 739         final PropertyName imageName = new PropertyName("image"); //NOI18N
 740         final PropertyName fitWidthName = new PropertyName("fitWidth"); //NOI18N
 741         final PropertyName fitHeightName = new PropertyName("fitHeight"); //NOI18N
 742 
 743         final ComponentClassMetadata imageViewMeta
 744                 = Metadata.getMetadata().queryComponentMetadata(ImageView.class);
 745         final PropertyMetadata imagePropMeta
 746                 = imageViewMeta.lookupProperty(imageName);
 747         final PropertyMetadata fitWidthPropMeta
 748                 = imageViewMeta.lookupProperty(fitWidthName);
 749         final PropertyMetadata fitHeightPropMeta
 750                 = imageViewMeta.lookupProperty(fitHeightName);
 751 
 752         assert imagePropMeta instanceof ImagePropertyMetadata;
 753         assert fitWidthPropMeta instanceof DoublePropertyMetadata;
 754         assert fitHeightPropMeta instanceof DoublePropertyMetadata;
 755 
 756         final ImagePropertyMetadata imageMeta
 757                 = (ImagePropertyMetadata) imagePropMeta;
 758         final DoublePropertyMetadata fitWidthMeta
 759                 = (DoublePropertyMetadata) fitWidthPropMeta;
 760         final DoublePropertyMetadata fitHeightMeta
 761                 = (DoublePropertyMetadata) fitHeightPropMeta;
 762 
 763         imageMeta.setValue(imageView, new DesignImage(image));
 764         fitWidthMeta.setValue(imageView, fitWidth);
 765         fitHeightMeta.setValue(imageView, fitHeight);
 766 
 767         result.setFxomRoot(imageView);
 768 
 769         return result;
 770     }
 771 
 772     private static FXOMDocument makeFxomDocumentFromMedia(
 773             Media media, double fitSize) throws IOException {
 774 
 775         assert media != null;
 776         assert fitSize > 0.0;
 777 
 778         final double mediaWidth = media.getWidth();
 779         final double mediaHeight = media.getHeight();
 780 
 781         final double fitWidth, fitHeight;
 782         final double mediaSize = Math.max(mediaWidth, mediaHeight);
 783         if (mediaSize < fitSize) {
 784             fitWidth = 0;
 785             fitHeight = 0;
 786         } else {
 787             final double widthScale  = fitSize / mediaSize;
 788             final double heightScale = fitSize / mediaHeight;
 789             final double scale = Math.min(widthScale, heightScale);
 790             fitWidth = Math.floor(mediaWidth * scale);
 791             fitHeight = Math.floor(mediaHeight * scale);
 792         }
 793 
 794         return makeFxomDocumentFromMedia(media, fitWidth, fitHeight);
 795     }
 796 
 797     private static FXOMDocument makeFxomDocumentFromMedia(
 798             Media media, double fitWidth, double fitHeight) {
 799 
 800         /*
 801          * <MediaView fitWidth="200" fitHeight="2003 >
 802          *   <mediaPlayer>
 803          *     <MediaPlayer cycleCount="-1">
 804          *       <media>
 805          *         <Media>
 806          *           <source>
 807          *              <URL value="file:/Users/elp/Dekstop/blah.flv" />
 808          *           </source>
 809          *         <Media/>
 810          *       </media>
 811          *     </MediaPlayer>
 812          *   </mediaPlayer>
 813          * </MediaView>
 814          */
 815 
 816         final FXOMDocument result = new FXOMDocument();
 817 
 818         /*
 819          * URL
 820          */
 821         final PropertyName valueName
 822                 = new PropertyName("value"); //NOI18N
 823         final FXOMPropertyT valueProperty
 824                 = new FXOMPropertyT(result, valueName, media.getSource());
 825         final FXOMInstance urlInstance
 826                 = new FXOMInstance(result, URL.class);
 827         valueProperty.addToParentInstance(-1, urlInstance);
 828 
 829         /*
 830          * Media
 831          */
 832         final PropertyName sourceName
 833                 = new PropertyName("source"); //NOI18N
 834         final FXOMPropertyC sourceProperty
 835                 = new FXOMPropertyC(result, sourceName, urlInstance);
 836         final FXOMInstance mediaInstance
 837                 = new FXOMInstance(result, Media.class);
 838         sourceProperty.addToParentInstance(-1, mediaInstance);
 839 
 840         /*
 841          * MediaPlayer
 842          */
 843         final PropertyName mediaName
 844                 = new PropertyName("media"); //NOI18N
 845         final FXOMPropertyC mediaProperty
 846                 = new FXOMPropertyC(result, mediaName, mediaInstance);
 847         final FXOMInstance mediaPlayerInstance
 848                 = new FXOMInstance(result, MediaPlayer.class);
 849         mediaProperty.addToParentInstance(-1, mediaPlayerInstance);
 850 
 851         /*
 852          * MediaView
 853          */
 854         final PropertyName mediaPlayerName
 855                 = new PropertyName("mediaPlayer"); //NOI18N
 856         final FXOMPropertyC mediaPlayerProperty
 857                 = new FXOMPropertyC(result, mediaPlayerName, mediaPlayerInstance);
 858         final PropertyName fitWidthName
 859                 = new PropertyName("fitWidth"); //NOI18N
 860         final FXOMPropertyT fitWidthProperty
 861                 = new FXOMPropertyT(result, fitWidthName, String.valueOf(fitWidth));
 862         final PropertyName fitHeightName
 863                 = new PropertyName("fitHeight"); //NOI18N
 864         final FXOMPropertyT fitHeightProperty
 865                 = new FXOMPropertyT(result, fitHeightName, String.valueOf(fitHeight));
 866         final FXOMInstance mediaView
 867                 = new FXOMInstance(result, MediaView.class);
 868         mediaPlayerProperty.addToParentInstance(-1, mediaView);
 869         fitWidthProperty.addToParentInstance(-1, mediaView);
 870         fitHeightProperty.addToParentInstance(-1, mediaView);
 871 
 872         result.setFxomRoot(mediaView);
 873 
 874         return result;
 875     }
 876 
 877 
 878     private static void serializeObjects(FXOMObject fxomObject, List<FXOMObject> result) {
 879         assert fxomObject != null;
 880         assert result != null;
 881 
 882         result.add(fxomObject);
 883 
 884         if (fxomObject instanceof FXOMInstance) {
 885             final FXOMInstance fxomInstance = (FXOMInstance) fxomObject;
 886             for (FXOMProperty p : fxomInstance.getProperties().values()) {
 887                 if (p instanceof FXOMPropertyC) {
 888                     final FXOMPropertyC pc = (FXOMPropertyC) p;
 889                     for (FXOMObject v : pc.getValues()) {
 890                         serializeObjects(v, result);
 891                     }
 892                 }
 893             }
 894         } else if (fxomObject instanceof FXOMCollection) {
 895             final FXOMCollection fxomCollection = (FXOMCollection) fxomObject;
 896             for (FXOMObject i : fxomCollection.getItems()) {
 897                 serializeObjects(i, result);
 898             }
 899         }
 900     }
 901 }