001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.file; 029 030import org.opencms.main.CmsRuntimeException; 031import org.opencms.util.CmsStringUtil; 032 033import java.io.Serializable; 034import java.util.ArrayList; 035import java.util.Collections; 036import java.util.HashMap; 037import java.util.Iterator; 038import java.util.LinkedHashMap; 039import java.util.List; 040import java.util.Map; 041import java.util.RandomAccess; 042 043/** 044 * Represents a property (meta-information) mapped to a VFS resource.<p> 045 * 046 * A property is an object that contains three string values: a name, a property value which is mapped 047 * to the structure record of a resource, and a property value which is mapped to the resource 048 * record of a resource. A property object is valid if it has both values or just one value set. 049 * Each property needs at least a name and one value set.<p> 050 * 051 * A property value mapped to the structure record of a resource is significant for a single 052 * resource (sibling). A property value mapped to the resource record of a resource is significant 053 * for all siblings of a resource record. This is possible by getting the "compound value" 054 * (see {@link #getValue()}) of a property in case a property object has both values set. The compound 055 * value of a property object is the value mapped to the structure record, because it's structure 056 * value is more significant than it's resource value. This allows to set a property only one time 057 * on the resource record, and the property takes effect on all siblings of this resource record.<p> 058 * 059 * The ID of the structure or resource record where a property value is mapped to is represented by 060 * the "PROPERTY_MAPPING_ID" table attribute in the database. The "PROPERTY_MAPPING_TYPE" table 061 * attribute (see {@link #STRUCTURE_RECORD_MAPPING} and {@link #RESOURCE_RECORD_MAPPING}) 062 * determines whether the value of the "PROPERTY_MAPPING_ID" attribute of the current row is 063 * a structure or resource record ID.<p> 064 * 065 * Property objects are written to the database using {@link org.opencms.file.CmsObject#writePropertyObject(String, CmsProperty)} 066 * or {@link org.opencms.file.CmsObject#writePropertyObjects(String, List)}, no matter 067 * whether you want to save a new (non-existing) property, update an existing property, or delete an 068 * existing property. To delete a property you would write a property object with either the 069 * structure and/or resource record values set to {@link #DELETE_VALUE} to indicate that a 070 * property value should be deleted in the database. Set property values to null if they should 071 * remain unchanged in the database when a property object is written. As for example you want to 072 * update just the structure value of a property, you would set the structure value to the new string, 073 * and the resource value to null (which is already the case by default).<p> 074 * 075 * Use {@link #setAutoCreatePropertyDefinition(boolean)} to set a boolean flag whether a missing property 076 * definition should be created implicitly for a resource type when a property is written to the database. 077 * The default value for this flag is <code>false</code>. Thus, you receive a CmsException if you try 078 * to write a property of a resource with a resource type which lacks a property definition for 079 * this resource type. It is not a good style to set {@link #setAutoCreatePropertyDefinition(boolean)} 080 * on true to make writing properties to the database work in any case, because then you will loose 081 * control about which resource types support which property definitions.<p> 082 * 083 * @since 6.0.0 084 */ 085public class CmsProperty implements Serializable, Cloneable, Comparable<CmsProperty> { 086 087 /** 088 * Signals that the resource property values of a resource 089 * should be deleted using deleteAllProperties.<p> 090 */ 091 public static final int DELETE_OPTION_DELETE_RESOURCE_VALUES = 3; 092 093 /** 094 * Signals that both the structure and resource property values of a resource 095 * should be deleted using deleteAllProperties.<p> 096 */ 097 public static final int DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES = 1; 098 099 /** 100 * Signals that the structure property values of a resource 101 * should be deleted using deleteAllProperties.<p> 102 */ 103 public static final int DELETE_OPTION_DELETE_STRUCTURE_VALUES = 2; 104 105 /** 106 * An empty string to decide that a property value should be deleted when this 107 * property object is written to the database.<p> 108 */ 109 public static final String DELETE_VALUE = ""; 110 111 /** 112 * Value of the "mapping-type" database attribute to indicate that a property value is mapped 113 * to a resource record.<p> 114 */ 115 public static final int RESOURCE_RECORD_MAPPING = 2; 116 117 /** 118 * Value of the "mapping-type" database attribute to indicate that a property value is mapped 119 * to a structure record.<p> 120 */ 121 public static final int STRUCTURE_RECORD_MAPPING = 1; 122 123 /** Key used for a individual (structure) property value. */ 124 public static final String TYPE_INDIVIDUAL = "individual"; 125 126 /** Key used for a shared (resource) property value. */ 127 public static final String TYPE_SHARED = "shared"; 128 129 /** The delimiter value for separating values in a list, per default this is the <code>|</code> char. */ 130 public static final char VALUE_LIST_DELIMITER = '|'; 131 132 /** The list delimiter replacement String used if the delimiter itself is contained in a String value. */ 133 public static final String VALUE_LIST_DELIMITER_REPLACEMENT = "%(ld)"; 134 135 /** The delimiter value for separating values in a map, per default this is the <code>=</code> char. */ 136 public static final char VALUE_MAP_DELIMITER = '='; 137 138 /** The map delimiter replacement String used if the delimiter itself is contained in a String value. */ 139 public static final String VALUE_MAP_DELIMITER_REPLACEMENT = "%(md)"; 140 141 /** The null property object to be used in caches if a property is not found. */ 142 private static final CmsProperty NULL_PROPERTY = new CmsProperty(); 143 144 /** Serial version UID required for safe serialization. */ 145 private static final long serialVersionUID = 93613508924212782L; 146 147 /** 148 * Static initializer required for freezing the <code>{@link #NULL_PROPERTY}</code>.<p> 149 */ 150 static { 151 152 NULL_PROPERTY.m_frozen = true; 153 NULL_PROPERTY.m_name = ""; 154 } 155 156 /** 157 * Boolean flag to decide if the property definition for this property should be created 158 * implicitly on any write operation if doesn't exist already.<p> 159 */ 160 private boolean m_autoCreatePropertyDefinition; 161 162 /** Indicates if the property is frozen (required for <code>NULL_PROPERTY</code>). */ 163 private boolean m_frozen; 164 165 /** The name of this property. */ 166 private String m_name; 167 168 /** The origin root path of the property. */ 169 private String m_origin; 170 171 /** The value of this property attached to the resource record. */ 172 private String m_resourceValue; 173 174 /** The (optional) value list of this property attached to the resource record. */ 175 private List<String> m_resourceValueList; 176 177 /** The (optional) value map of this property attached to the resource record. */ 178 private Map<String, String> m_resourceValueMap; 179 180 /** The value of this property attached to the structure record. */ 181 private String m_structureValue; 182 183 /** The (optional) value list of this property attached to the structure record. */ 184 private List<String> m_structureValueList; 185 186 /** The (optional) value map of this property attached to the structure record. */ 187 private Map<String, String> m_structureValueMap; 188 189 /** 190 * Creates a new CmsProperty object.<p> 191 * 192 * The structure and resource property values are initialized to null. The structure and 193 * resource IDs are initialized to {@link org.opencms.util.CmsUUID#getNullUUID()}.<p> 194 */ 195 public CmsProperty() { 196 197 // nothing to do, all values will be initialized with <code>null</code> or <code>false</code> by default 198 } 199 200 /** 201 * Creates a new CmsProperty object using the provided values.<p> 202 * 203 * If the property definition does not exist for the resource type it 204 * is automatically created when this property is written. 205 * 206 * @param name the name of the property definition 207 * @param structureValue the value to write as structure property 208 * @param resourceValue the value to write as resource property 209 */ 210 public CmsProperty(String name, String structureValue, String resourceValue) { 211 212 this(name, structureValue, resourceValue, true); 213 } 214 215 /** 216 * Creates a new CmsProperty object using the provided values.<p> 217 * 218 * If <code>null</code> is supplied for the resource or structure value, this 219 * value will not be available for this property.<p> 220 * 221 * @param name the name of the property definition 222 * @param structureValue the value to write as structure property, or <code>null</code> 223 * @param resourceValue the value to write as resource property , or <code>null</code> 224 * @param autoCreatePropertyDefinition if <code>true</code>, the property definition for this property will be 225 * created implicitly on any write operation if it doesn't exist already 226 */ 227 public CmsProperty(String name, String structureValue, String resourceValue, boolean autoCreatePropertyDefinition) { 228 229 m_name = name.trim(); 230 m_structureValue = structureValue; 231 m_resourceValue = resourceValue; 232 m_autoCreatePropertyDefinition = autoCreatePropertyDefinition; 233 } 234 235 /** 236 * Searches in a list for the first occurrence of a {@link CmsProperty} object with the given name.<p> 237 * 238 * To check if the "null property" has been returned if a property was 239 * not found, use {@link #isNullProperty()} on the result.<p> 240 * 241 * @param name a property name 242 * @param list a list of {@link CmsProperty} objects 243 * @return the index of the first occurrence of the name in they specified list, 244 * or {@link CmsProperty#getNullProperty()} if the name is not found 245 */ 246 public static final CmsProperty get(String name, List<CmsProperty> list) { 247 248 CmsProperty property = null; 249 name = name.trim(); 250 // choose the fastest method to traverse the list 251 if (list instanceof RandomAccess) { 252 for (int i = 0, n = list.size(); i < n; i++) { 253 property = list.get(i); 254 if (property.m_name.equals(name)) { 255 return property; 256 } 257 } 258 } else { 259 Iterator<CmsProperty> i = list.iterator(); 260 while (i.hasNext()) { 261 property = i.next(); 262 if (property.m_name.equals(name)) { 263 return property; 264 } 265 } 266 } 267 268 return NULL_PROPERTY; 269 } 270 271 /** 272 * Returns the null property object.<p> 273 * 274 * @return the null property object 275 */ 276 public static final CmsProperty getNullProperty() { 277 278 return NULL_PROPERTY; 279 } 280 281 /** 282 * Transforms a list of CmsProperty objects with structure and resource values into a map with 283 * CmsProperty object values keyed by property keys.<p> 284 * 285 * @param list a list of CmsProperty objects 286 * @return a map with CmsPropery object values keyed by property keys 287 */ 288 public static Map<String, CmsProperty> getPropertyMap(List<CmsProperty> list) { 289 290 Map<String, CmsProperty> result = null; 291 String key = null; 292 CmsProperty property = null; 293 294 if ((list == null) || (list.size() == 0)) { 295 return Collections.emptyMap(); 296 } 297 298 result = new HashMap<String, CmsProperty>(); 299 300 // choose the fastest method to iterate the list 301 if (list instanceof RandomAccess) { 302 for (int i = 0, n = list.size(); i < n; i++) { 303 property = list.get(i); 304 key = property.getName(); 305 result.put(key, property); 306 } 307 } else { 308 Iterator<CmsProperty> i = list.iterator(); 309 while (i.hasNext()) { 310 property = i.next(); 311 key = property.getName(); 312 result.put(key, property); 313 } 314 } 315 316 return result; 317 } 318 319 /** 320 * Calls <code>{@link #setAutoCreatePropertyDefinition(boolean)}</code> for each 321 * property object in the given List with the given <code>value</code> parameter.<p> 322 * 323 * This method will modify the objects in the input list directly.<p> 324 * 325 * @param list a list of {@link CmsProperty} objects to modify 326 * @param value boolean value 327 * 328 * @return the modified list of {@link CmsProperty} objects 329 * 330 * @see #setAutoCreatePropertyDefinition(boolean) 331 */ 332 public static final List<CmsProperty> setAutoCreatePropertyDefinitions(List<CmsProperty> list, boolean value) { 333 334 CmsProperty property; 335 336 // choose the fastest method to traverse the list 337 if (list instanceof RandomAccess) { 338 for (int i = 0, n = list.size(); i < n; i++) { 339 property = list.get(i); 340 property.m_autoCreatePropertyDefinition = value; 341 } 342 } else { 343 Iterator<CmsProperty> i = list.iterator(); 344 while (i.hasNext()) { 345 property = i.next(); 346 property.m_autoCreatePropertyDefinition = value; 347 } 348 } 349 350 return list; 351 } 352 353 /** 354 * Calls <code>{@link #setFrozen(boolean)}</code> for each 355 * {@link CmsProperty} object in the given List if it is not already frozen.<p> 356 * 357 * This method will modify the objects in the input list directly.<p> 358 * 359 * @param list a list of {@link CmsProperty} objects 360 * 361 * @return the modified list of properties 362 * 363 * @see #setFrozen(boolean) 364 */ 365 public static final List<CmsProperty> setFrozen(List<CmsProperty> list) { 366 367 CmsProperty property; 368 369 // choose the fastest method to traverse the list 370 if (list instanceof RandomAccess) { 371 for (int i = 0, n = list.size(); i < n; i++) { 372 property = list.get(i); 373 if (!property.isFrozen()) { 374 property.setFrozen(true); 375 } 376 } 377 } else { 378 Iterator<CmsProperty> i = list.iterator(); 379 while (i.hasNext()) { 380 property = i.next(); 381 if (!property.isFrozen()) { 382 property.setFrozen(true); 383 } 384 } 385 } 386 387 return list; 388 } 389 390 /** 391 * Transforms a Map of String values into a list of 392 * {@link CmsProperty} objects with the property name set from the 393 * Map key, and the structure value set from the Map value.<p> 394 * 395 * @param map a Map with String keys and String values 396 * 397 * @return a list of {@link CmsProperty} objects 398 */ 399 public static List<CmsProperty> toList(Map<String, String> map) { 400 401 if ((map == null) || (map.size() == 0)) { 402 return Collections.emptyList(); 403 } 404 405 List<CmsProperty> result = new ArrayList<CmsProperty>(map.size()); 406 Iterator<Map.Entry<String, String>> i = map.entrySet().iterator(); 407 while (i.hasNext()) { 408 Map.Entry<String, String> e = i.next(); 409 CmsProperty property = new CmsProperty(e.getKey(), e.getValue(), null); 410 result.add(property); 411 } 412 413 return result; 414 } 415 416 /** 417 * Transforms a list of {@link CmsProperty} objects into a Map which uses the property name as 418 * Map key (String), and the property value as Map value (String).<p> 419 * 420 * @param list a list of {@link CmsProperty} objects 421 * 422 * @return a Map which uses the property names as 423 * Map keys (String), and the property values as Map values (String) 424 */ 425 public static Map<String, String> toMap(List<CmsProperty> list) { 426 427 if ((list == null) || (list.size() == 0)) { 428 return Collections.emptyMap(); 429 } 430 431 String name = null; 432 String value = null; 433 CmsProperty property = null; 434 Map<String, String> result = new HashMap<String, String>(list.size()); 435 436 // choose the fastest method to traverse the list 437 if (list instanceof RandomAccess) { 438 for (int i = 0, n = list.size(); i < n; i++) { 439 property = list.get(i); 440 name = property.m_name; 441 value = property.getValue(); 442 result.put(name, value); 443 } 444 } else { 445 Iterator<CmsProperty> i = list.iterator(); 446 while (i.hasNext()) { 447 property = i.next(); 448 name = property.m_name; 449 value = property.getValue(); 450 result.put(name, value); 451 } 452 } 453 454 return result; 455 } 456 457 /** 458 * Stores a collection of properties in a map, with the property names as keys.<p> 459 * 460 * @param properties the properties to store in the map 461 * 462 * @return the map with the property names as keys and the property objects as values 463 */ 464 public static Map<String, CmsProperty> toObjectMap(Iterable<CmsProperty> properties) { 465 466 Map<String, CmsProperty> result = new LinkedHashMap<String, CmsProperty>(); 467 for (CmsProperty property : properties) { 468 result.put(property.getName(), property); 469 } 470 return result; 471 } 472 473 /** 474 * Wraps a null value into a null property, and returns all other values unchanged.<p> 475 * 476 * @param prop the value to wrap 477 * 478 * @return a wrapped null property, or the original prop if it wasn't null 479 */ 480 public static CmsProperty wrapIfNull(CmsProperty prop) { 481 482 if (prop == null) { 483 return getNullProperty(); 484 } else { 485 return prop; 486 } 487 } 488 489 /** 490 * Checks if the property definition for this property will be 491 * created implicitly on any write operation if doesn't already exist.<p> 492 * 493 * @return <code>true</code>, if the property definition for this property will be created implicitly on any write operation 494 */ 495 public boolean autoCreatePropertyDefinition() { 496 497 return m_autoCreatePropertyDefinition; 498 } 499 500 /** 501 * Creates a clone of this property.<p> 502 * 503 * @return a clone of this property 504 * 505 * @see #cloneAsProperty() 506 */ 507 @Override 508 public CmsProperty clone() { 509 510 return cloneAsProperty(); 511 } 512 513 /** 514 * Creates a clone of this property that already is of type <code>{@link CmsProperty}</code>.<p> 515 * 516 * The cloned property will not be frozen.<p> 517 * 518 * @return a clone of this property that already is of type <code>{@link CmsProperty}</code> 519 */ 520 public CmsProperty cloneAsProperty() { 521 522 if (this == NULL_PROPERTY) { 523 // null property must never be cloned 524 return NULL_PROPERTY; 525 } 526 CmsProperty clone = new CmsProperty(); 527 clone.m_name = m_name; 528 clone.m_structureValue = m_structureValue; 529 clone.m_structureValueList = m_structureValueList; 530 clone.m_resourceValue = m_resourceValue; 531 clone.m_resourceValueList = m_resourceValueList; 532 clone.m_autoCreatePropertyDefinition = m_autoCreatePropertyDefinition; 533 clone.m_origin = m_origin; 534 // the value for m_frozen does not need to be set as it is false by default 535 536 return clone; 537 } 538 539 /** 540 * Compares this property to another Object.<p> 541 * 542 * @param obj the other object to be compared 543 * @return if the argument is a property object, returns zero if the name of the argument is equal to the name of this property object, 544 * a value less than zero if the name of this property is lexicographically less than the name of the argument, 545 * or a value greater than zero if the name of this property is lexicographically greater than the name of the argument 546 */ 547 public int compareTo(CmsProperty obj) { 548 549 if (obj == this) { 550 return 0; 551 } 552 return m_name.compareTo(obj.m_name); 553 } 554 555 /** 556 * Tests if a specified object is equal to this CmsProperty object.<p> 557 * 558 * Two property objects are equal if their names are equal.<p> 559 * 560 * In case you want to compare the values as well as the name, 561 * use {@link #isIdentical(CmsProperty)} instead.<p> 562 * 563 * @param obj another object 564 * @return true, if the specified object is equal to this CmsProperty object 565 * 566 * @see #isIdentical(CmsProperty) 567 */ 568 @Override 569 public boolean equals(Object obj) { 570 571 if (obj == this) { 572 return true; 573 } 574 if (obj instanceof CmsProperty) { 575 return ((CmsProperty)obj).m_name.equals(m_name); 576 } 577 return false; 578 } 579 580 /** 581 * Returns the name of this property.<p> 582 * 583 * @return the name of this property 584 */ 585 public String getName() { 586 587 return m_name; 588 } 589 590 /** 591 * Returns the root path of the resource from which the property was read.<p> 592 * 593 * @return the root path of the resource from which the property was read 594 */ 595 public String getOrigin() { 596 597 return m_origin; 598 } 599 600 /** 601 * Returns the value of this property attached to the resource record.<p> 602 * 603 * @return the value of this property attached to the resource record 604 */ 605 public String getResourceValue() { 606 607 return m_resourceValue; 608 } 609 610 /** 611 * Returns the value of this property attached to the resource record, split as a list.<p> 612 * 613 * This list is build form the resource value, which is split into separate values 614 * using the <code>|</code> char as delimiter. If the delimiter is not found, 615 * then the list will contain one entry which is equal to <code>{@link #getResourceValue()}</code>.<p> 616 * 617 * @return the value of this property attached to the resource record, split as a (unmodifiable) list of Strings 618 */ 619 public List<String> getResourceValueList() { 620 621 if ((m_resourceValueList == null) && (m_resourceValue != null)) { 622 // use lazy initializing of the list 623 m_resourceValueList = createListFromValue(m_resourceValue); 624 m_resourceValueList = Collections.unmodifiableList(m_resourceValueList); 625 } 626 return m_resourceValueList; 627 } 628 629 /** 630 * Returns the value of this property attached to the resource record as a map.<p> 631 * 632 * This map is build from the used value, which is split into separate key/value pairs 633 * using the <code>|</code> char as delimiter. If the delimiter is not found, 634 * then the map will contain one entry.<p> 635 * 636 * The key/value pairs are separated with the <code>=</code>.<p> 637 * 638 * @return the value of this property attached to the resource record, as an (unmodifiable) map of Strings 639 */ 640 public Map<String, String> getResourceValueMap() { 641 642 if ((m_resourceValueMap == null) && (m_resourceValue != null)) { 643 // use lazy initializing of the map 644 m_resourceValueMap = createMapFromValue(m_resourceValue); 645 m_resourceValueMap = Collections.unmodifiableMap(m_resourceValueMap); 646 } 647 return m_resourceValueMap; 648 } 649 650 /** 651 * Returns the value of this property attached to the structure record.<p> 652 * 653 * @return the value of this property attached to the structure record 654 */ 655 public String getStructureValue() { 656 657 return m_structureValue; 658 } 659 660 /** 661 * Returns the value of this property attached to the structure record, split as a list.<p> 662 * 663 * This list is build form the structure value, which is split into separate values 664 * using the <code>|</code> char as delimiter. If the delimiter is not found, 665 * then the list will contain one entry which is equal to <code>{@link #getStructureValue()}</code>.<p> 666 * 667 * @return the value of this property attached to the structure record, split as a (unmodifiable) list of Strings 668 */ 669 public List<String> getStructureValueList() { 670 671 if ((m_structureValueList == null) && (m_structureValue != null)) { 672 // use lazy initializing of the list 673 m_structureValueList = createListFromValue(m_structureValue); 674 m_structureValueList = Collections.unmodifiableList(m_structureValueList); 675 } 676 return m_structureValueList; 677 } 678 679 /** 680 * Returns the value of this property attached to the structure record as a map.<p> 681 * 682 * This map is build from the used value, which is split into separate key/value pairs 683 * using the <code>|</code> char as delimiter. If the delimiter is not found, 684 * then the map will contain one entry.<p> 685 * 686 * The key/value pairs are separated with the <code>=</code>.<p> 687 * 688 * @return the value of this property attached to the structure record, as an (unmodifiable) map of Strings 689 */ 690 public Map<String, String> getStructureValueMap() { 691 692 if ((m_structureValueMap == null) && (m_structureValue != null)) { 693 // use lazy initializing of the map 694 m_structureValueMap = createMapFromValue(m_structureValue); 695 m_structureValueMap = Collections.unmodifiableMap(m_structureValueMap); 696 } 697 return m_structureValueMap; 698 } 699 700 /** 701 * Returns the compound value of this property.<p> 702 * 703 * The value returned is the value of {@link #getStructureValue()}, if it is not <code>null</code>. 704 * Otherwise the value if {@link #getResourceValue()} is returned (which may also be <code>null</code>).<p> 705 * 706 * @return the compound value of this property 707 */ 708 public String getValue() { 709 710 return (m_structureValue != null) ? m_structureValue : m_resourceValue; 711 } 712 713 /** 714 * Returns the compound value of this property, or a specified default value, 715 * if both the structure and resource values are null.<p> 716 * 717 * In other words, this method returns the defaultValue if this property object 718 * is the null property (see {@link CmsProperty#getNullProperty()}).<p> 719 * 720 * @param defaultValue a default value which is returned if both the structure and resource values are <code>null</code> 721 * 722 * @return the compound value of this property, or the default value 723 */ 724 public String getValue(String defaultValue) { 725 726 if (this == CmsProperty.NULL_PROPERTY) { 727 // return the default value if this property is the null property 728 return defaultValue; 729 } 730 731 // somebody might have set both values to null manually 732 // on a property object different from the null property... 733 return (m_structureValue != null) 734 ? m_structureValue 735 : ((m_resourceValue != null) ? m_resourceValue : defaultValue); 736 } 737 738 /** 739 * Returns the compound value of this property, split as a list.<p> 740 * 741 * This list is build form the used value, which is split into separate values 742 * using the <code>|</code> char as delimiter. If the delimiter is not found, 743 * then the list will contain one entry.<p> 744 * 745 * The value returned is the value of {@link #getStructureValueList()}, if it is not <code>null</code>. 746 * Otherwise the value of {@link #getResourceValueList()} is returned (which may also be <code>null</code>).<p> 747 * 748 * @return the compound value of this property, split as a (unmodifiable) list of Strings 749 */ 750 public List<String> getValueList() { 751 752 return (m_structureValue != null) ? getStructureValueList() : getResourceValueList(); 753 } 754 755 /** 756 * Returns the compound value of this property, split as a list, or a specified default value list, 757 * if both the structure and resource values are null.<p> 758 * 759 * In other words, this method returns the defaultValue if this property object 760 * is the null property (see {@link CmsProperty#getNullProperty()}).<p> 761 * 762 * @param defaultValue a default value list which is returned if both the structure and resource values are <code>null</code> 763 * 764 * @return the compound value of this property, split as a (unmodifiable) list of Strings 765 */ 766 public List<String> getValueList(List<String> defaultValue) { 767 768 if (this == CmsProperty.NULL_PROPERTY) { 769 // return the default value if this property is the null property 770 return defaultValue; 771 } 772 773 // somebody might have set both values to null manually 774 // on a property object different from the null property... 775 return (m_structureValue != null) 776 ? getStructureValueList() 777 : ((m_resourceValue != null) ? getResourceValueList() : defaultValue); 778 } 779 780 /** 781 * Returns the compound value of this property as a map.<p> 782 * 783 * This map is build from the used value, which is split into separate key/value pairs 784 * using the <code>|</code> char as delimiter. If the delimiter is not found, 785 * then the map will contain one entry.<p> 786 * 787 * The key/value pairs are separated with the <code>=</code>.<p> 788 * 789 * The value returned is the value of {@link #getStructureValueMap()}, if it is not <code>null</code>. 790 * Otherwise the value of {@link #getResourceValueMap()} is returned (which may also be <code>null</code>).<p> 791 * 792 * @return the compound value of this property as a (unmodifiable) map of Strings 793 */ 794 public Map<String, String> getValueMap() { 795 796 return (m_structureValue != null) ? getStructureValueMap() : getResourceValueMap(); 797 } 798 799 /** 800 * Returns the compound value of this property as a map, or a specified default value map, 801 * if both the structure and resource values are null.<p> 802 * 803 * In other words, this method returns the defaultValue if this property object 804 * is the null property (see {@link CmsProperty#getNullProperty()}).<p> 805 * 806 * @param defaultValue a default value map which is returned if both the structure and resource values are <code>null</code> 807 * 808 * @return the compound value of this property as a (unmodifiable) map of Strings 809 */ 810 public Map<String, String> getValueMap(Map<String, String> defaultValue) { 811 812 if (this == CmsProperty.NULL_PROPERTY) { 813 // return the default value if this property is the null property 814 return defaultValue; 815 } 816 817 // somebody might have set both values to null manually 818 // on a property object different from the null property... 819 return (m_structureValue != null) 820 ? getStructureValueMap() 821 : ((m_resourceValue != null) ? getResourceValueMap() : defaultValue); 822 } 823 824 /** 825 * Returns the hash code of the property, which is based only on the property name, not on the values.<p> 826 * 827 * The resource and structure values are not taken into consideration for the hashcode generation 828 * because the {@link #equals(Object)} implementation also does not take these into consideration.<p> 829 * 830 * @return the hash code of the property 831 * 832 * @see java.lang.Object#hashCode() 833 */ 834 @Override 835 public int hashCode() { 836 837 return m_name.hashCode(); 838 } 839 840 /** 841 * Checks if the resource value of this property should be deleted when this 842 * property object is written to the database.<p> 843 * 844 * @return true, if the resource value of this property should be deleted 845 * @see CmsProperty#DELETE_VALUE 846 */ 847 public boolean isDeleteResourceValue() { 848 849 return (m_resourceValue == DELETE_VALUE) || ((m_resourceValue != null) && (m_resourceValue.length() == 0)); 850 } 851 852 /** 853 * Checks if the structure value of this property should be deleted when this 854 * property object is written to the database.<p> 855 * 856 * @return true, if the structure value of this property should be deleted 857 * @see CmsProperty#DELETE_VALUE 858 */ 859 public boolean isDeleteStructureValue() { 860 861 return (m_structureValue == DELETE_VALUE) || ((m_structureValue != null) && (m_structureValue.length() == 0)); 862 } 863 864 /** 865 * Returns <code>true</code> if this property is frozen, that is read only.<p> 866 * 867 * @return <code>true</code> if this property is frozen, that is read only 868 */ 869 public boolean isFrozen() { 870 871 return m_frozen; 872 } 873 874 /** 875 * Tests if a given CmsProperty is identical to this CmsProperty object.<p> 876 * 877 * The property object are identical if their name, structure and 878 * resource values are all equals.<p> 879 * 880 * @param property another property object 881 * @return true, if the specified object is equal to this CmsProperty object 882 */ 883 public boolean isIdentical(CmsProperty property) { 884 885 boolean isEqual; 886 887 // compare the name 888 if (m_name == null) { 889 isEqual = (property.getName() == null); 890 } else { 891 isEqual = m_name.equals(property.getName()); 892 } 893 894 // compare the structure value 895 if (m_structureValue == null) { 896 isEqual &= (property.getStructureValue() == null); 897 } else { 898 isEqual &= m_structureValue.equals(property.getStructureValue()); 899 } 900 901 // compare the resource value 902 if (m_resourceValue == null) { 903 isEqual &= (property.getResourceValue() == null); 904 } else { 905 isEqual &= m_resourceValue.equals(property.getResourceValue()); 906 } 907 908 return isEqual; 909 } 910 911 /** 912 * Checks if this property object is the null property object.<p> 913 * 914 * @return true if this property object is the null property object 915 */ 916 public boolean isNullProperty() { 917 918 return NULL_PROPERTY.equals(this); 919 } 920 921 /** 922 * Sets the boolean flag to decide if the property definition for this property should be 923 * created implicitly on any write operation if doesn't exist already.<p> 924 * 925 * @param value true, if the property definition for this property should be created implicitly on any write operation 926 */ 927 public void setAutoCreatePropertyDefinition(boolean value) { 928 929 checkFrozen(); 930 m_autoCreatePropertyDefinition = value; 931 } 932 933 /** 934 * Sets the frozen state of the property, if set to <code>true</code> then this property is read only.<p> 935 * 936 * If the property is already frozen, then setting the frozen state to <code>true</code> again is allowed, 937 * but setting the value to <code>false</code> causes a <code>{@link CmsRuntimeException}</code>.<p> 938 * 939 * @param frozen the frozen state to set 940 */ 941 public void setFrozen(boolean frozen) { 942 943 if (!frozen) { 944 checkFrozen(); 945 } 946 m_frozen = frozen; 947 } 948 949 /** 950 * Sets the name of this property.<p> 951 * 952 * @param name the name to set 953 */ 954 public void setName(String name) { 955 956 checkFrozen(); 957 m_name = name.trim(); 958 } 959 960 /** 961 * Sets the path of the resource from which the property was read.<p> 962 * 963 * @param originRootPath the root path of the root path from which the property was read 964 */ 965 public void setOrigin(String originRootPath) { 966 967 checkFrozen(); 968 m_origin = originRootPath; 969 } 970 971 /** 972 * Sets the value of this property attached to the resource record.<p> 973 * 974 * @param resourceValue the value of this property attached to the resource record 975 */ 976 public void setResourceValue(String resourceValue) { 977 978 checkFrozen(); 979 m_resourceValue = resourceValue; 980 m_resourceValueList = null; 981 } 982 983 /** 984 * Sets the value of this property attached to the resource record from the given list of Strings.<p> 985 * 986 * The value will be created from the individual values of the given list, which are appended 987 * using the <code>|</code> char as delimiter.<p> 988 * 989 * @param valueList the list of value (Strings) to attach to the resource record 990 */ 991 public void setResourceValueList(List<String> valueList) { 992 993 checkFrozen(); 994 if (valueList != null) { 995 m_resourceValueList = new ArrayList<String>(valueList); 996 m_resourceValueList = Collections.unmodifiableList(m_resourceValueList); 997 m_resourceValue = createValueFromList(m_resourceValueList); 998 } else { 999 m_resourceValueList = null; 1000 m_resourceValue = null; 1001 } 1002 } 1003 1004 /** 1005 * Sets the value of this property attached to the resource record from the given map of Strings.<p> 1006 * 1007 * The value will be created from the individual values of the given map, which are appended 1008 * using the <code>|</code> char as delimiter, the map keys and values are separated by a <code>=</code>.<p> 1009 * 1010 * @param valueMap the map of key/value (Strings) to attach to the resource record 1011 */ 1012 public void setResourceValueMap(Map<String, String> valueMap) { 1013 1014 checkFrozen(); 1015 if (valueMap != null) { 1016 m_resourceValueMap = new HashMap<String, String>(valueMap); 1017 m_resourceValueMap = Collections.unmodifiableMap(m_resourceValueMap); 1018 m_resourceValue = createValueFromMap(m_resourceValueMap); 1019 } else { 1020 m_resourceValueMap = null; 1021 m_resourceValue = null; 1022 } 1023 } 1024 1025 /** 1026 * Sets the value of this property attached to the structure record.<p> 1027 * 1028 * @param structureValue the value of this property attached to the structure record 1029 */ 1030 public void setStructureValue(String structureValue) { 1031 1032 checkFrozen(); 1033 m_structureValue = structureValue; 1034 m_structureValueList = null; 1035 } 1036 1037 /** 1038 * Sets the value of this property attached to the structure record from the given list of Strings.<p> 1039 * 1040 * The value will be created from the individual values of the given list, which are appended 1041 * using the <code>|</code> char as delimiter.<p> 1042 * 1043 * @param valueList the list of value (Strings) to attach to the structure record 1044 */ 1045 public void setStructureValueList(List<String> valueList) { 1046 1047 checkFrozen(); 1048 if (valueList != null) { 1049 m_structureValueList = new ArrayList<String>(valueList); 1050 m_structureValueList = Collections.unmodifiableList(m_structureValueList); 1051 m_structureValue = createValueFromList(m_structureValueList); 1052 } else { 1053 m_structureValueList = null; 1054 m_structureValue = null; 1055 } 1056 } 1057 1058 /** 1059 * Sets the value of this property attached to the structure record from the given map of Strings.<p> 1060 * 1061 * The value will be created from the individual values of the given map, which are appended 1062 * using the <code>|</code> char as delimiter, the map keys and values are separated by a <code>=</code>.<p> 1063 * 1064 * @param valueMap the map of key/value (Strings) to attach to the structure record 1065 */ 1066 public void setStructureValueMap(Map<String, String> valueMap) { 1067 1068 checkFrozen(); 1069 if (valueMap != null) { 1070 m_structureValueMap = new HashMap<String, String>(valueMap); 1071 m_structureValueMap = Collections.unmodifiableMap(m_structureValueMap); 1072 m_structureValue = createValueFromMap(m_structureValueMap); 1073 } else { 1074 m_structureValueMap = null; 1075 m_structureValue = null; 1076 } 1077 } 1078 1079 /** 1080 * Sets the value of this property as either shared or 1081 * individual value.<p> 1082 * 1083 * If the given type equals {@link CmsProperty#TYPE_SHARED} then 1084 * the value is set as a shared (resource) value, otherwise it 1085 * is set as individual (structure) value.<p> 1086 * 1087 * @param value the value to set 1088 * @param type the value type to set 1089 */ 1090 public void setValue(String value, String type) { 1091 1092 checkFrozen(); 1093 setAutoCreatePropertyDefinition(true); 1094 if (TYPE_SHARED.equalsIgnoreCase(type)) { 1095 // set the provided value as shared (resource) value 1096 setResourceValue(value); 1097 } else { 1098 // set the provided value as individual (structure) value 1099 setStructureValue(value); 1100 } 1101 } 1102 1103 /** 1104 * Returns a string representation of this property object.<p> 1105 * 1106 * @see java.lang.Object#toString() 1107 */ 1108 @Override 1109 public String toString() { 1110 1111 StringBuffer strBuf = new StringBuffer(); 1112 1113 strBuf.append("[").append(getClass().getName()).append(": "); 1114 strBuf.append("name: '").append(m_name).append("'"); 1115 strBuf.append(", value: '").append(getValue()).append("'"); 1116 strBuf.append(", structure value: '").append(m_structureValue).append("'"); 1117 strBuf.append(", resource value: '").append(m_resourceValue).append("'"); 1118 strBuf.append(", frozen: ").append(m_frozen); 1119 strBuf.append(", origin: ").append(m_origin); 1120 strBuf.append("]"); 1121 1122 return strBuf.toString(); 1123 } 1124 1125 /** 1126 * Checks if this property is frozen, that is read only.<p> 1127 */ 1128 private void checkFrozen() { 1129 1130 if (m_frozen) { 1131 throw new CmsRuntimeException(Messages.get().container(Messages.ERR_PROPERTY_FROZEN_1, toString())); 1132 } 1133 } 1134 1135 /** 1136 * Returns the list value representation for the given String.<p> 1137 * 1138 * The given value is split along the <code>|</code> char.<p> 1139 * 1140 * @param value the value to create the list representation for 1141 * 1142 * @return the list value representation for the given String 1143 */ 1144 private List<String> createListFromValue(String value) { 1145 1146 if (value == null) { 1147 return null; 1148 } 1149 List<String> result = CmsStringUtil.splitAsList(value, VALUE_LIST_DELIMITER); 1150 if (value.indexOf(VALUE_LIST_DELIMITER_REPLACEMENT) != -1) { 1151 List<String> tempList = new ArrayList<String>(result.size()); 1152 Iterator<String> i = result.iterator(); 1153 while (i.hasNext()) { 1154 String item = i.next(); 1155 tempList.add(rebuildDelimiter(item, VALUE_LIST_DELIMITER, VALUE_LIST_DELIMITER_REPLACEMENT)); 1156 } 1157 result = tempList; 1158 } 1159 1160 return result; 1161 } 1162 1163 /** 1164 * Returns the map value representation for the given String.<p> 1165 * 1166 * The given value is split along the <code>|</code> char, the map keys and values are separated by a <code>=</code>.<p> 1167 * 1168 * @param value the value to create the map representation for 1169 * 1170 * @return the map value representation for the given String 1171 */ 1172 private Map<String, String> createMapFromValue(String value) { 1173 1174 if (value == null) { 1175 return null; 1176 } 1177 List<String> entries = createListFromValue(value); 1178 Iterator<String> i = entries.iterator(); 1179 Map<String, String> result = new HashMap<String, String>(entries.size()); 1180 boolean rebuildDelimiters = false; 1181 if (value.indexOf(VALUE_MAP_DELIMITER_REPLACEMENT) != -1) { 1182 rebuildDelimiters = true; 1183 } 1184 while (i.hasNext()) { 1185 String entry = i.next(); 1186 int index = entry.indexOf(VALUE_MAP_DELIMITER); 1187 if (index != -1) { 1188 String key = entry.substring(0, index); 1189 String val = ""; 1190 if ((index + 1) < entry.length()) { 1191 val = entry.substring(index + 1); 1192 } 1193 if (CmsStringUtil.isNotEmpty(key)) { 1194 if (rebuildDelimiters) { 1195 key = rebuildDelimiter(key, VALUE_MAP_DELIMITER, VALUE_MAP_DELIMITER_REPLACEMENT); 1196 val = rebuildDelimiter(val, VALUE_MAP_DELIMITER, VALUE_MAP_DELIMITER_REPLACEMENT); 1197 } 1198 result.put(key, val); 1199 } 1200 } 1201 } 1202 return result; 1203 } 1204 1205 /** 1206 * Returns the single String value representation for the given value list.<p> 1207 * 1208 * @param valueList the value list to create the single String value for 1209 * 1210 * @return the single String value representation for the given value list 1211 */ 1212 private String createValueFromList(List<String> valueList) { 1213 1214 if (valueList == null) { 1215 return null; 1216 } 1217 StringBuffer result = new StringBuffer(valueList.size() * 32); 1218 Iterator<String> i = valueList.iterator(); 1219 while (i.hasNext()) { 1220 result.append( 1221 replaceDelimiter(i.next().toString(), VALUE_LIST_DELIMITER, VALUE_LIST_DELIMITER_REPLACEMENT)); 1222 if (i.hasNext()) { 1223 result.append(VALUE_LIST_DELIMITER); 1224 } 1225 } 1226 return result.toString(); 1227 } 1228 1229 /** 1230 * Returns the single String value representation for the given value map.<p> 1231 * 1232 * @param valueMap the value map to create the single String value for 1233 * 1234 * @return the single String value representation for the given value map 1235 */ 1236 private String createValueFromMap(Map<String, String> valueMap) { 1237 1238 if (valueMap == null) { 1239 return null; 1240 } 1241 StringBuffer result = new StringBuffer(valueMap.size() * 32); 1242 Iterator<Map.Entry<String, String>> i = valueMap.entrySet().iterator(); 1243 while (i.hasNext()) { 1244 Map.Entry<String, String> entry = i.next(); 1245 String key = entry.getKey(); 1246 String value = entry.getValue(); 1247 key = replaceDelimiter(key, VALUE_LIST_DELIMITER, VALUE_LIST_DELIMITER_REPLACEMENT); 1248 key = replaceDelimiter(key, VALUE_MAP_DELIMITER, VALUE_MAP_DELIMITER_REPLACEMENT); 1249 value = replaceDelimiter(value, VALUE_LIST_DELIMITER, VALUE_LIST_DELIMITER_REPLACEMENT); 1250 value = replaceDelimiter(value, VALUE_MAP_DELIMITER, VALUE_MAP_DELIMITER_REPLACEMENT); 1251 result.append(key); 1252 result.append(VALUE_MAP_DELIMITER); 1253 result.append(value); 1254 if (i.hasNext()) { 1255 result.append(VALUE_LIST_DELIMITER); 1256 } 1257 } 1258 return result.toString(); 1259 } 1260 1261 /** 1262 * Rebuilds the given delimiter character from the replacement string.<p> 1263 * 1264 * @param value the string that is scanned 1265 * @param delimiter the delimiter character to rebuild 1266 * @param delimiterReplacement the replacement string for the delimiter character 1267 * @return the substituted string 1268 */ 1269 private String rebuildDelimiter(String value, char delimiter, String delimiterReplacement) { 1270 1271 return CmsStringUtil.substitute(value, delimiterReplacement, String.valueOf(delimiter)); 1272 } 1273 1274 /** 1275 * Replaces the given delimiter character with the replacement string.<p> 1276 * 1277 * @param value the string that is scanned 1278 * @param delimiter the delimiter character to replace 1279 * @param delimiterReplacement the replacement string for the delimiter character 1280 * @return the substituted string 1281 */ 1282 private String replaceDelimiter(String value, char delimiter, String delimiterReplacement) { 1283 1284 return CmsStringUtil.substitute(value, String.valueOf(delimiter), delimiterReplacement); 1285 } 1286 1287}