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.property.value.list.StringListPropertyMetadata;
  36 import com.oracle.javafx.scenebuilder.kit.metadata.util.PrefixedValue;
  37 import com.oracle.javafx.scenebuilder.kit.metadata.util.PropertyName;
  38 import java.net.MalformedURLException;
  39 import java.net.URL;
  40 import java.util.ArrayList;
  41 import java.util.List;
  42 import java.util.Map;
  43 
  44 /**
  45  *
  46  *
  47  */
  48 public class FXOMPropertyT extends FXOMProperty {
  49 
  50     /*
  51      * A FXOMPropertyT represents a property with a single value that
  52      * can be represented as text : String, Boolean, Integer, Double, Enum...
  53      *
  54      * There are three ways to express such a property in FXML.
  55      * Let's take Button.text as an example:
  56      *
  57      * 1) <Button text="OK" />
  58      *      value = "OK"
  59      *      propertyElement == null
  60      *      valueElement == null
  61      *
  62      * 2) <Button><text>OK</text></Button>
  63      *      value = "OK"
  64      *      propertyElement != null
  65      *          && propertyElement.getTagName() == "text"
  66      *          && propertyElement.getChildren().size() == 0
  67      *          && propertyElement.getContentText().equals("OK")
  68      *
  69      * 3) <Button><text><String fx:value="OK"/></text></Button>
  70      *      value = "OK"
  71      *      propertyElement != null
  72      *          && propertyElement.getTagName() == "text"
  73      *          && propertyElement.getChildren().size() == 1
  74      *          && propertyElement.getChildren().get(0) == valueElement
  75      *      valueElement != null
  76      *          && valueElement.getAttributes().get("fx:value").equals("OK")
  77      */
  78 
  79     private String value;
  80     private final GlueElement propertyElement;
  81     private final GlueElement valueElement;
  82 
  83     public FXOMPropertyT(FXOMDocument document, PropertyName name, GlueElement propertyElement, GlueElement valueElement, String value) {
  84         super(document, name);
  85         this.propertyElement = propertyElement;
  86         this.valueElement = valueElement;
  87         this.value = value;
  88     }
  89 
  90     public FXOMPropertyT(FXOMDocument document, PropertyName name, String value) {
  91         super(document, name);
  92         assert value != null;
  93         this.propertyElement = null;
  94         this.valueElement = null;
  95         this.value = value;
  96     }
  97 
  98     public String getValue() {
  99         return value;
 100     }
 101 
 102     public void setValue(String newValue) {
 103         assert newValue != null;
 104 
 105 
 106         if (propertyElement != null) {
 107             if (valueElement != null) { // Case #3
 108                 final Map<String,String> attributes = valueElement.getAttributes();
 109                 assert attributes.get("fx:value") != null;
 110                 assert attributes.get("fx:value").equals(value);
 111                 attributes.put("fx:value", newValue);
 112 
 113             } else { // Case #2
 114                 assert propertyElement.getContentText() != null;
 115                 assert propertyElement.getContentText().equals(value);
 116                 propertyElement.setContentText(newValue);
 117             }
 118         } else { // Case #1
 119             final FXOMInstance parentInstance = getParentInstance();
 120             if (parentInstance != null) {
 121                 final GlueElement parentElement = parentInstance.getGlueElement();
 122                 final Map<String, String> parentAttributes = parentElement.getAttributes();
 123                 assert parentAttributes.get(getName().toString()).equals(value);
 124                 parentAttributes.put(getName().toString(), newValue);
 125             }
 126         }
 127 
 128         value = newValue;
 129     }
 130 
 131     public GlueElement getPropertyElement() {
 132         return propertyElement;
 133     }
 134 
 135     public GlueElement getValueElement() {
 136         return valueElement;
 137     }
 138 
 139     /*
 140      * FXOMProperty
 141      */
 142 
 143     @Override
 144     public void addToParentInstance(int index, FXOMInstance newParentInstance) {
 145 
 146         assert newParentInstance != null;
 147 
 148         if (getParentInstance() != null) {
 149             removeFromParentInstance();
 150         }
 151 
 152         setParentInstance(newParentInstance);
 153         newParentInstance.addProperty(this);
 154 
 155         final GlueElement newParentElement = newParentInstance.getGlueElement();
 156 
 157         if (propertyElement == null) { // Case #1
 158             // index is ignored
 159             final Map<String,String> attributes = newParentElement.getAttributes();
 160             assert attributes.get(getName().toString()) == null;
 161             attributes.put(getName().toString(), value);
 162         } else { // Case #2 or #3
 163             assert -1 <= index;
 164             assert index <= newParentElement.getChildren().size();
 165             propertyElement.addToParent(index, newParentElement);
 166         }
 167     }
 168 
 169 
 170     @Override
 171     public void removeFromParentInstance() {
 172 
 173         assert getParentInstance() != null;
 174 
 175         final FXOMInstance currentParentInstance = getParentInstance();
 176         final GlueElement currentParentElement = currentParentInstance.getGlueElement();
 177 
 178         if (propertyElement == null) { // Case #1
 179             final Map<String,String> attributes = currentParentElement.getAttributes();
 180             assert attributes.get(getName().toString()) != null;
 181             attributes.remove(getName().toString());
 182         } else { // Case #2 or #3
 183            propertyElement.removeFromParent();
 184         }
 185 
 186         setParentInstance(null);
 187         currentParentInstance.removeProperty(this);
 188     }
 189 
 190 
 191     @Override
 192     public int getIndexInParentInstance() {
 193         final int result;
 194 
 195         if (getParentInstance() == null) {
 196             result = -1;
 197         } else if (propertyElement == null) { // Case #1
 198             result = -1;
 199         } else { // Case #2 or #3
 200             final GlueElement parentElement = getParentInstance().getGlueElement();
 201             result = parentElement.getChildren().indexOf(propertyElement);
 202             assert result != -1;
 203         }
 204 
 205         return result;
 206     }
 207 
 208 
 209     /*
 210      * FXOMNode
 211      */
 212 
 213     @Override
 214     public void moveToFxomDocument(FXOMDocument destination) {
 215         assert destination != null;
 216         assert destination != getFxomDocument();
 217 
 218         documentLocationWillChange(destination.getLocation());
 219 
 220         if (getParentInstance() != null) {
 221             assert getParentInstance().getFxomDocument() == getFxomDocument();
 222             removeFromParentInstance();
 223         }
 224 
 225         assert getParentInstance() == null;
 226         assert (propertyElement == null) || (propertyElement.getParent() == null);
 227 
 228         if (propertyElement != null) {
 229             propertyElement.moveToDocument(destination.getGlue());
 230             assert (valueElement == null) || (valueElement.getDocument() == destination.getGlue());
 231         }
 232         changeFxomDocument(destination);
 233     }
 234 
 235 
 236     @Override
 237     protected void changeFxomDocument(FXOMDocument destination) {
 238         assert destination != null;
 239         assert destination != getFxomDocument();
 240         assert (propertyElement == null) || (destination.getGlue() == propertyElement.getDocument());
 241 
 242         super.changeFxomDocument(destination);
 243     }
 244 
 245     @Override
 246     public void documentLocationWillChange(URL newLocation) {
 247         final URL currentLocation = getFxomDocument().getLocation();
 248 
 249         final List<String> currentItems = StringListPropertyMetadata.splitValue(getValue());
 250         final List<String> newItems = new ArrayList<>();
 251         int changeCount = 0;
 252         for (String currentItem : currentItems) {
 253             final PrefixedValue pv = new PrefixedValue(currentItem);
 254             if (pv.isDocumentRelativePath()) {
 255                 assert currentLocation != null;
 256 
 257                 /*
 258                  * currentItem is a path relative to currentLocation.
 259                  * We compute the absolute path and, if new location
 260                  * is non null, we relativize the absolute path against
 261                  * newLocation.
 262                  */
 263                 final URL assetURL = pv.resolveDocumentRelativePath(currentLocation);
 264                 final String newValue;
 265                 if (newLocation == null) {
 266                     newValue = assetURL.toString();
 267                 } else {
 268                     final PrefixedValue pv2
 269                             = PrefixedValue.makePrefixedValue(assetURL, newLocation);
 270                     newValue = pv2.toString();
 271                 }
 272                 newItems.add(newValue);
 273                 changeCount++;
 274             } else if (pv.isPlainString() && (currentLocation == null)) {
 275 
 276                 /*
 277                  * currentItem is a plain string.
 278                  * We check if it is an URL.
 279                  *
 280                  * Since currentLocation is null and newLocation non null,
 281                  * then all URLs should be converted to relative path.
 282                  */
 283                 assert newLocation != null;
 284                 try {
 285                     final URL assetURL = new URL(pv.getSuffix());
 286                     final PrefixedValue pv2 = PrefixedValue.makePrefixedValue(assetURL, newLocation);
 287                     newItems.add(pv2.toString());
 288                     changeCount++;
 289                 } catch(MalformedURLException x) {
 290                     // p.getValue() is not an URL
 291                     // We keep currentItem unchanged.
 292                     newItems.add(currentItem);
 293                 }
 294             } else {
 295                 newItems.add(currentItem);
 296             }
 297         }
 298         assert currentItems.size() == newItems.size();
 299 
 300         if (changeCount >= 1) {
 301             setValue(StringListPropertyMetadata.assembleValue(newItems));
 302         }
 303 
 304     }
 305 
 306 }