org.springframework.beans.MutablePropertyValues Maven / Gradle / Ivy
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Spliterator;
import java.util.stream.Stream;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* The default implementation of the {@link PropertyValues} interface.
* Allows simple manipulation of properties, and provides constructors
* to support deep copy and construction from a Map.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @since 13 May 2001
*/
@SuppressWarnings("serial")
public class MutablePropertyValues implements PropertyValues, Serializable {
private final List propertyValueList;
@Nullable
private Set processedProperties;
private volatile boolean converted;
/**
* Creates a new empty MutablePropertyValues object.
* Property values can be added with the {@code add} method.
* @see #add(String, Object)
*/
public MutablePropertyValues() {
this.propertyValueList = new ArrayList<>(0);
}
/**
* Deep copy constructor. Guarantees PropertyValue references
* are independent, although it can't deep copy objects currently
* referenced by individual PropertyValue objects.
* @param original the PropertyValues to copy
* @see #addPropertyValues(PropertyValues)
*/
public MutablePropertyValues(@Nullable PropertyValues original) {
// We can optimize this because it's all new:
// There is no replacement of existing property values.
if (original != null) {
PropertyValue[] pvs = original.getPropertyValues();
this.propertyValueList = new ArrayList<>(pvs.length);
for (PropertyValue pv : pvs) {
this.propertyValueList.add(new PropertyValue(pv));
}
}
else {
this.propertyValueList = new ArrayList<>(0);
}
}
/**
* Construct a new MutablePropertyValues object from a Map.
* @param original a Map with property values keyed by property name Strings
* @see #addPropertyValues(Map)
*/
public MutablePropertyValues(@Nullable Map, ?> original) {
// We can optimize this because it's all new:
// There is no replacement of existing property values.
if (original != null) {
this.propertyValueList = new ArrayList<>(original.size());
original.forEach((attrName, attrValue) -> this.propertyValueList.add(
new PropertyValue(attrName.toString(), attrValue)));
}
else {
this.propertyValueList = new ArrayList<>(0);
}
}
/**
* Construct a new MutablePropertyValues object using the given List of
* PropertyValue objects as-is.
*
This is a constructor for advanced usage scenarios.
* It is not intended for typical programmatic use.
* @param propertyValueList a List of PropertyValue objects
*/
public MutablePropertyValues(@Nullable List propertyValueList) {
this.propertyValueList =
(propertyValueList != null ? propertyValueList : new ArrayList<>());
}
/**
* Return the underlying List of PropertyValue objects in its raw form.
* The returned List can be modified directly, although this is not recommended.
* This is an accessor for optimized access to all PropertyValue objects.
* It is not intended for typical programmatic use.
*/
public List getPropertyValueList() {
return this.propertyValueList;
}
/**
* Return the number of PropertyValue entries in the list.
*/
public int size() {
return this.propertyValueList.size();
}
/**
* Copy all given PropertyValues into this object. Guarantees PropertyValue
* references are independent, although it can't deep copy objects currently
* referenced by individual PropertyValue objects.
* @param other the PropertyValues to copy
* @return this in order to allow for adding multiple property values in a chain
*/
public MutablePropertyValues addPropertyValues(@Nullable PropertyValues other) {
if (other != null) {
PropertyValue[] pvs = other.getPropertyValues();
for (PropertyValue pv : pvs) {
addPropertyValue(new PropertyValue(pv));
}
}
return this;
}
/**
* Add all property values from the given Map.
* @param other a Map with property values keyed by property name,
* which must be a String
* @return this in order to allow for adding multiple property values in a chain
*/
public MutablePropertyValues addPropertyValues(@Nullable Map, ?> other) {
if (other != null) {
other.forEach((attrName, attrValue) -> addPropertyValue(
new PropertyValue(attrName.toString(), attrValue)));
}
return this;
}
/**
* Add a PropertyValue object, replacing any existing one for the
* corresponding property or getting merged with it (if applicable).
* @param pv the PropertyValue object to add
* @return this in order to allow for adding multiple property values in a chain
*/
public MutablePropertyValues addPropertyValue(PropertyValue pv) {
for (int i = 0; i < this.propertyValueList.size(); i++) {
PropertyValue currentPv = this.propertyValueList.get(i);
if (currentPv.getName().equals(pv.getName())) {
pv = mergeIfRequired(pv, currentPv);
setPropertyValueAt(pv, i);
return this;
}
}
this.propertyValueList.add(pv);
return this;
}
/**
* Overloaded version of {@code addPropertyValue} that takes
* a property name and a property value.
* Note: we recommend using the more concise and chaining-capable variant
* {@link #add(String, Object)}.
* @param propertyName name of the property
* @param propertyValue value of the property
* @see #addPropertyValue(PropertyValue)
*/
public void addPropertyValue(String propertyName, Object propertyValue) {
addPropertyValue(new PropertyValue(propertyName, propertyValue));
}
/**
* Add a PropertyValue object, replacing any existing one for the
* corresponding property or getting merged with it (if applicable).
* @param propertyName name of the property
* @param propertyValue value of the property
* @return this in order to allow for adding multiple property values in a chain
*/
public MutablePropertyValues add(String propertyName, @Nullable Object propertyValue) {
addPropertyValue(new PropertyValue(propertyName, propertyValue));
return this;
}
/**
* Modify a PropertyValue object held in this object.
* Indexed from 0.
*/
public void setPropertyValueAt(PropertyValue pv, int i) {
this.propertyValueList.set(i, pv);
}
/**
* Merges the value of the supplied 'new' {@link PropertyValue} with that of
* the current {@link PropertyValue} if merging is supported and enabled.
* @see Mergeable
*/
private PropertyValue mergeIfRequired(PropertyValue newPv, PropertyValue currentPv) {
Object value = newPv.getValue();
if (value instanceof Mergeable mergeable) {
if (mergeable.isMergeEnabled()) {
Object merged = mergeable.merge(currentPv.getValue());
return new PropertyValue(newPv.getName(), merged);
}
}
return newPv;
}
/**
* Remove the given PropertyValue, if contained.
* @param pv the PropertyValue to remove
*/
public void removePropertyValue(PropertyValue pv) {
this.propertyValueList.remove(pv);
}
/**
* Overloaded version of {@code removePropertyValue} that takes a property name.
* @param propertyName name of the property
* @see #removePropertyValue(PropertyValue)
*/
public void removePropertyValue(String propertyName) {
this.propertyValueList.remove(getPropertyValue(propertyName));
}
@Override
public Iterator iterator() {
return Collections.unmodifiableList(this.propertyValueList).iterator();
}
@Override
public Spliterator spliterator() {
return this.propertyValueList.spliterator();
}
@Override
public Stream stream() {
return this.propertyValueList.stream();
}
@Override
public PropertyValue[] getPropertyValues() {
return this.propertyValueList.toArray(new PropertyValue[0]);
}
@Override
@Nullable
public PropertyValue getPropertyValue(String propertyName) {
for (PropertyValue pv : this.propertyValueList) {
if (pv.getName().equals(propertyName)) {
return pv;
}
}
return null;
}
/**
* Get the raw property value, if any.
* @param propertyName the name to search for
* @return the raw property value, or {@code null} if none found
* @since 4.0
* @see #getPropertyValue(String)
* @see PropertyValue#getValue()
*/
@Nullable
public Object get(String propertyName) {
PropertyValue pv = getPropertyValue(propertyName);
return (pv != null ? pv.getValue() : null);
}
@Override
public PropertyValues changesSince(PropertyValues old) {
MutablePropertyValues changes = new MutablePropertyValues();
if (old == this) {
return changes;
}
// for each property value in the new set
for (PropertyValue newPv : this.propertyValueList) {
// if there wasn't an old one, add it
PropertyValue pvOld = old.getPropertyValue(newPv.getName());
if (pvOld == null || !pvOld.equals(newPv)) {
changes.addPropertyValue(newPv);
}
}
return changes;
}
@Override
public boolean contains(String propertyName) {
return (getPropertyValue(propertyName) != null ||
(this.processedProperties != null && this.processedProperties.contains(propertyName)));
}
@Override
public boolean isEmpty() {
return this.propertyValueList.isEmpty();
}
/**
* Register the specified property as "processed" in the sense
* of some processor calling the corresponding setter method
* outside the PropertyValue(s) mechanism.
* This will lead to {@code true} being returned from
* a {@link #contains} call for the specified property.
* @param propertyName the name of the property.
*/
public void registerProcessedProperty(String propertyName) {
if (this.processedProperties == null) {
this.processedProperties = new HashSet<>(4);
}
this.processedProperties.add(propertyName);
}
/**
* Clear the "processed" registration of the given property, if any.
* @since 3.2.13
*/
public void clearProcessedProperty(String propertyName) {
if (this.processedProperties != null) {
this.processedProperties.remove(propertyName);
}
}
/**
* Mark this holder as containing converted values only
* (i.e. no runtime resolution needed anymore).
*/
public void setConverted() {
this.converted = true;
}
/**
* Return whether this holder contains converted values only ({@code true}),
* or whether the values still need to be converted ({@code false}).
*/
public boolean isConverted() {
return this.converted;
}
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof MutablePropertyValues that &&
this.propertyValueList.equals(that.propertyValueList)));
}
@Override
public int hashCode() {
return this.propertyValueList.hashCode();
}
@Override
public String toString() {
PropertyValue[] pvs = getPropertyValues();
if (pvs.length > 0) {
return "PropertyValues: length=" + pvs.length + "; " + StringUtils.arrayToDelimitedString(pvs, "; ");
}
return "PropertyValues: length=0";
}
}