/*
* Copyright 2002-2018 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
*
* http://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.
HashSet;
import java.util.
List;
import java.util.
Map;
import java.util.
Set;
import org.springframework.util.
StringUtils;
/**
* 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<
PropertyValue>
propertyValueList;
private
Set<
String>
processedProperties;
private volatile boolean
converted = false;
/**
* Creates a new empty MutablePropertyValues object.
* <p>Property values can be added with the {@code add} method.
* @see #add(String, Object)
*/
public
MutablePropertyValues() {
this.
propertyValueList = new
ArrayList<
PropertyValue>(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(
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<
PropertyValue>(
pvs.length);
for (
PropertyValue pv :
pvs) {
this.
propertyValueList.
add(new
PropertyValue(
pv));
}
}
else {
this.
propertyValueList = new
ArrayList<
PropertyValue>(0);
}
}
/**
* Construct a new MutablePropertyValues object from a Map.
* @param original Map with property values keyed by property name Strings
* @see #addPropertyValues(Map)
*/
public
MutablePropertyValues(
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<
PropertyValue>(
original.
size());
for (
Map.
Entry<?, ?>
entry :
original.
entrySet()) {
this.
propertyValueList.
add(new
PropertyValue(
entry.
getKey().
toString(),
entry.
getValue()));
}
}
else {
this.
propertyValueList = new
ArrayList<
PropertyValue>(0);
}
}
/**
* Construct a new MutablePropertyValues object using the given List of
* PropertyValue objects as-is.
* <p>This is a constructor for advanced usage scenarios.
* It is not intended for typical programmatic use.
* @param propertyValueList List of PropertyValue objects
*/
public
MutablePropertyValues(
List<
PropertyValue>
propertyValueList) {
this.
propertyValueList =
(
propertyValueList != null ?
propertyValueList : new
ArrayList<
PropertyValue>());
}
/**
* Return the underlying List of PropertyValue objects in its raw form.
* The returned List can be modified directly, although this is not recommended.
* <p>This is an accessor for optimized access to all PropertyValue objects.
* It is not intended for typical programmatic use.
*/
public
List<
PropertyValue>
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(
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 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(
Map<?, ?>
other) {
if (
other != null) {
for (
Map.
Entry<?, ?>
entry :
other.
entrySet()) {
addPropertyValue(new
PropertyValue(
entry.
getKey().
toString(),
entry.
getValue()));
}
}
return this;
}
/**
* Add a PropertyValue object, replacing any existing one for the
* corresponding property or getting merged with it (if applicable).
* @param pv 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.
* <p>Note: As of Spring 3.0, we recommend using the more concise
* and chaining-capable variant {@link #add}.
* @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,
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 mergeable = (
Mergeable)
value;
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
PropertyValue[]
getPropertyValues() {
return this.
propertyValueList.
toArray(new
PropertyValue[this.
propertyValueList.
size()]);
}
@
Override
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()
*/
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 of the PropertyValue(s) mechanism.
* <p>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<
String>();
}
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(
Object other) {
if (this ==
other) {
return true;
}
if (!(
other instanceof
MutablePropertyValues)) {
return false;
}
MutablePropertyValues that = (
MutablePropertyValues)
other;
return this.
propertyValueList.
equals(
that.
propertyValueList);
}
@
Override
public int
hashCode() {
return this.
propertyValueList.
hashCode();
}
@
Override
public
String toString() {
PropertyValue[]
pvs =
getPropertyValues();
StringBuilder sb = new
StringBuilder("PropertyValues: length=").
append(
pvs.length);
if (
pvs.length > 0) {
sb.
append("; ").
append(
StringUtils.
arrayToDelimitedString(
pvs, "; "));
}
return
sb.
toString();
}
}