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