001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.beans; 018 019import java.io.Serializable; 020import java.util.ArrayList; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026import org.springframework.util.StringUtils; 027 028/** 029 * Default implementation of the {@link PropertyValues} interface. 030 * Allows simple manipulation of properties, and provides constructors 031 * to support deep copy and construction from a Map. 032 * 033 * @author Rod Johnson 034 * @author Juergen Hoeller 035 * @author Rob Harrop 036 * @since 13 May 2001 037 */ 038@SuppressWarnings("serial") 039public class MutablePropertyValues implements PropertyValues, Serializable { 040 041 private final List<PropertyValue> propertyValueList; 042 043 private Set<String> processedProperties; 044 045 private volatile boolean converted = false; 046 047 048 /** 049 * Creates a new empty MutablePropertyValues object. 050 * <p>Property values can be added with the {@code add} method. 051 * @see #add(String, Object) 052 */ 053 public MutablePropertyValues() { 054 this.propertyValueList = new ArrayList<PropertyValue>(0); 055 } 056 057 /** 058 * Deep copy constructor. Guarantees PropertyValue references 059 * are independent, although it can't deep copy objects currently 060 * referenced by individual PropertyValue objects. 061 * @param original the PropertyValues to copy 062 * @see #addPropertyValues(PropertyValues) 063 */ 064 public MutablePropertyValues(PropertyValues original) { 065 // We can optimize this because it's all new: 066 // There is no replacement of existing property values. 067 if (original != null) { 068 PropertyValue[] pvs = original.getPropertyValues(); 069 this.propertyValueList = new ArrayList<PropertyValue>(pvs.length); 070 for (PropertyValue pv : pvs) { 071 this.propertyValueList.add(new PropertyValue(pv)); 072 } 073 } 074 else { 075 this.propertyValueList = new ArrayList<PropertyValue>(0); 076 } 077 } 078 079 /** 080 * Construct a new MutablePropertyValues object from a Map. 081 * @param original Map with property values keyed by property name Strings 082 * @see #addPropertyValues(Map) 083 */ 084 public MutablePropertyValues(Map<?, ?> original) { 085 // We can optimize this because it's all new: 086 // There is no replacement of existing property values. 087 if (original != null) { 088 this.propertyValueList = new ArrayList<PropertyValue>(original.size()); 089 for (Map.Entry<?, ?> entry : original.entrySet()) { 090 this.propertyValueList.add(new PropertyValue(entry.getKey().toString(), entry.getValue())); 091 } 092 } 093 else { 094 this.propertyValueList = new ArrayList<PropertyValue>(0); 095 } 096 } 097 098 /** 099 * Construct a new MutablePropertyValues object using the given List of 100 * PropertyValue objects as-is. 101 * <p>This is a constructor for advanced usage scenarios. 102 * It is not intended for typical programmatic use. 103 * @param propertyValueList List of PropertyValue objects 104 */ 105 public MutablePropertyValues(List<PropertyValue> propertyValueList) { 106 this.propertyValueList = 107 (propertyValueList != null ? propertyValueList : new ArrayList<PropertyValue>()); 108 } 109 110 111 /** 112 * Return the underlying List of PropertyValue objects in its raw form. 113 * The returned List can be modified directly, although this is not recommended. 114 * <p>This is an accessor for optimized access to all PropertyValue objects. 115 * It is not intended for typical programmatic use. 116 */ 117 public List<PropertyValue> getPropertyValueList() { 118 return this.propertyValueList; 119 } 120 121 /** 122 * Return the number of PropertyValue entries in the list. 123 */ 124 public int size() { 125 return this.propertyValueList.size(); 126 } 127 128 /** 129 * Copy all given PropertyValues into this object. Guarantees PropertyValue 130 * references are independent, although it can't deep copy objects currently 131 * referenced by individual PropertyValue objects. 132 * @param other the PropertyValues to copy 133 * @return this in order to allow for adding multiple property values in a chain 134 */ 135 public MutablePropertyValues addPropertyValues(PropertyValues other) { 136 if (other != null) { 137 PropertyValue[] pvs = other.getPropertyValues(); 138 for (PropertyValue pv : pvs) { 139 addPropertyValue(new PropertyValue(pv)); 140 } 141 } 142 return this; 143 } 144 145 /** 146 * Add all property values from the given Map. 147 * @param other Map with property values keyed by property name, 148 * which must be a String 149 * @return this in order to allow for adding multiple property values in a chain 150 */ 151 public MutablePropertyValues addPropertyValues(Map<?, ?> other) { 152 if (other != null) { 153 for (Map.Entry<?, ?> entry : other.entrySet()) { 154 addPropertyValue(new PropertyValue(entry.getKey().toString(), entry.getValue())); 155 } 156 } 157 return this; 158 } 159 160 /** 161 * Add a PropertyValue object, replacing any existing one for the 162 * corresponding property or getting merged with it (if applicable). 163 * @param pv PropertyValue object to add 164 * @return this in order to allow for adding multiple property values in a chain 165 */ 166 public MutablePropertyValues addPropertyValue(PropertyValue pv) { 167 for (int i = 0; i < this.propertyValueList.size(); i++) { 168 PropertyValue currentPv = this.propertyValueList.get(i); 169 if (currentPv.getName().equals(pv.getName())) { 170 pv = mergeIfRequired(pv, currentPv); 171 setPropertyValueAt(pv, i); 172 return this; 173 } 174 } 175 this.propertyValueList.add(pv); 176 return this; 177 } 178 179 /** 180 * Overloaded version of {@code addPropertyValue} that takes 181 * a property name and a property value. 182 * <p>Note: As of Spring 3.0, we recommend using the more concise 183 * and chaining-capable variant {@link #add}. 184 * @param propertyName name of the property 185 * @param propertyValue value of the property 186 * @see #addPropertyValue(PropertyValue) 187 */ 188 public void addPropertyValue(String propertyName, Object propertyValue) { 189 addPropertyValue(new PropertyValue(propertyName, propertyValue)); 190 } 191 192 /** 193 * Add a PropertyValue object, replacing any existing one for the 194 * corresponding property or getting merged with it (if applicable). 195 * @param propertyName name of the property 196 * @param propertyValue value of the property 197 * @return this in order to allow for adding multiple property values in a chain 198 */ 199 public MutablePropertyValues add(String propertyName, Object propertyValue) { 200 addPropertyValue(new PropertyValue(propertyName, propertyValue)); 201 return this; 202 } 203 204 /** 205 * Modify a PropertyValue object held in this object. 206 * Indexed from 0. 207 */ 208 public void setPropertyValueAt(PropertyValue pv, int i) { 209 this.propertyValueList.set(i, pv); 210 } 211 212 /** 213 * Merges the value of the supplied 'new' {@link PropertyValue} with that of 214 * the current {@link PropertyValue} if merging is supported and enabled. 215 * @see Mergeable 216 */ 217 private PropertyValue mergeIfRequired(PropertyValue newPv, PropertyValue currentPv) { 218 Object value = newPv.getValue(); 219 if (value instanceof Mergeable) { 220 Mergeable mergeable = (Mergeable) value; 221 if (mergeable.isMergeEnabled()) { 222 Object merged = mergeable.merge(currentPv.getValue()); 223 return new PropertyValue(newPv.getName(), merged); 224 } 225 } 226 return newPv; 227 } 228 229 /** 230 * Remove the given PropertyValue, if contained. 231 * @param pv the PropertyValue to remove 232 */ 233 public void removePropertyValue(PropertyValue pv) { 234 this.propertyValueList.remove(pv); 235 } 236 237 /** 238 * Overloaded version of {@code removePropertyValue} that takes a property name. 239 * @param propertyName name of the property 240 * @see #removePropertyValue(PropertyValue) 241 */ 242 public void removePropertyValue(String propertyName) { 243 this.propertyValueList.remove(getPropertyValue(propertyName)); 244 } 245 246 247 @Override 248 public PropertyValue[] getPropertyValues() { 249 return this.propertyValueList.toArray(new PropertyValue[this.propertyValueList.size()]); 250 } 251 252 @Override 253 public PropertyValue getPropertyValue(String propertyName) { 254 for (PropertyValue pv : this.propertyValueList) { 255 if (pv.getName().equals(propertyName)) { 256 return pv; 257 } 258 } 259 return null; 260 } 261 262 /** 263 * Get the raw property value, if any. 264 * @param propertyName the name to search for 265 * @return the raw property value, or {@code null} if none found 266 * @since 4.0 267 * @see #getPropertyValue(String) 268 * @see PropertyValue#getValue() 269 */ 270 public Object get(String propertyName) { 271 PropertyValue pv = getPropertyValue(propertyName); 272 return (pv != null ? pv.getValue() : null); 273 } 274 275 @Override 276 public PropertyValues changesSince(PropertyValues old) { 277 MutablePropertyValues changes = new MutablePropertyValues(); 278 if (old == this) { 279 return changes; 280 } 281 282 // for each property value in the new set 283 for (PropertyValue newPv : this.propertyValueList) { 284 // if there wasn't an old one, add it 285 PropertyValue pvOld = old.getPropertyValue(newPv.getName()); 286 if (pvOld == null || !pvOld.equals(newPv)) { 287 changes.addPropertyValue(newPv); 288 } 289 } 290 return changes; 291 } 292 293 @Override 294 public boolean contains(String propertyName) { 295 return (getPropertyValue(propertyName) != null || 296 (this.processedProperties != null && this.processedProperties.contains(propertyName))); 297 } 298 299 @Override 300 public boolean isEmpty() { 301 return this.propertyValueList.isEmpty(); 302 } 303 304 305 /** 306 * Register the specified property as "processed" in the sense 307 * of some processor calling the corresponding setter method 308 * outside of the PropertyValue(s) mechanism. 309 * <p>This will lead to {@code true} being returned from 310 * a {@link #contains} call for the specified property. 311 * @param propertyName the name of the property. 312 */ 313 public void registerProcessedProperty(String propertyName) { 314 if (this.processedProperties == null) { 315 this.processedProperties = new HashSet<String>(); 316 } 317 this.processedProperties.add(propertyName); 318 } 319 320 /** 321 * Clear the "processed" registration of the given property, if any. 322 * @since 3.2.13 323 */ 324 public void clearProcessedProperty(String propertyName) { 325 if (this.processedProperties != null) { 326 this.processedProperties.remove(propertyName); 327 } 328 } 329 330 /** 331 * Mark this holder as containing converted values only 332 * (i.e. no runtime resolution needed anymore). 333 */ 334 public void setConverted() { 335 this.converted = true; 336 } 337 338 /** 339 * Return whether this holder contains converted values only ({@code true}), 340 * or whether the values still need to be converted ({@code false}). 341 */ 342 public boolean isConverted() { 343 return this.converted; 344 } 345 346 347 @Override 348 public boolean equals(Object other) { 349 if (this == other) { 350 return true; 351 } 352 if (!(other instanceof MutablePropertyValues)) { 353 return false; 354 } 355 MutablePropertyValues that = (MutablePropertyValues) other; 356 return this.propertyValueList.equals(that.propertyValueList); 357 } 358 359 @Override 360 public int hashCode() { 361 return this.propertyValueList.hashCode(); 362 } 363 364 @Override 365 public String toString() { 366 PropertyValue[] pvs = getPropertyValues(); 367 StringBuilder sb = new StringBuilder("PropertyValues: length=").append(pvs.length); 368 if (pvs.length > 0) { 369 sb.append("; ").append(StringUtils.arrayToDelimitedString(pvs, "; ")); 370 } 371 return sb.toString(); 372 } 373 374}