All Downloads are FREE. Search and download functionalities are using the official Maven repository.

infra.beans.PropertyValues Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 - 2024 the original author or authors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see [https://www.gnu.org/licenses/]
 */
package infra.beans;

import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;

import infra.lang.Assert;
import infra.lang.Nullable;
import infra.util.CollectionUtils;
import infra.util.ObjectUtils;
import infra.util.StringUtils;

/**
 * Holder containing one or more {@link PropertyValue} objects,
 * typically comprising one update for a specific target bean.
 * 

* this implementation Allows simple manipulation of properties, * and provides constructors to support deep copy and construction from a Map. * * @author Harry Yang * @since 4.0 2021/12/1 14:49 */ public class PropertyValues implements Iterable, Serializable { @Serial private static final long serialVersionUID = 1L; @Nullable private ArrayList propertyValues; private volatile boolean converted; @Nullable private Set processedProperties; /** * Creates a new empty PropertyValues object. *

Property values can be added with the {@code add} method. * * @see #add(String, Object) */ public PropertyValues() { } /** * 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 #set(PropertyValues) */ public PropertyValues(@Nullable PropertyValues original) { // We can optimize this because it's all new: // There is no replacement of existing property values. if (original != null) { set(original); } } /** * Construct a new PropertyValues object from a Map. * * @param original a Map with property values keyed by property name Strings * @see #set(Map) */ @SuppressWarnings({ "rawtypes", "unchecked" }) public PropertyValues(@Nullable Map original) { set(original); } /** * Construct a new PropertyValues 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 PropertyValues(@Nullable List propertyValueList) { set(propertyValueList); } /** * Return the number of PropertyValue entries in the list. */ public int size() { return propertyValues == null ? 0 : propertyValues().size(); } /** * Remove the given PropertyValue, if contained. * * @param pv the PropertyValue to remove */ public void remove(PropertyValue pv) { if (propertyValues != null) { propertyValues.remove(pv); } } /** * Overloaded version of {@code removePropertyValue} that takes a property name. * * @param propertyName name of the property * @see #remove(PropertyValue) */ public void remove(String propertyName) { if (propertyValues != null) { PropertyValue propertyValue = get(propertyName); if (propertyValue != null) { propertyValues.remove(propertyValue); } } } /** * @param propertyName the name to search for * @return the raw property value, or {@code null} if none found * @see #getPropertyValue(String) * @see PropertyValue#getValue() */ @Nullable public Object getPropertyValue(String propertyName) { PropertyValue pv = get(propertyName); return pv != null ? pv.getValue() : 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 * @see #getPropertyValue(String) * @see PropertyValue#getValue() */ @Nullable public PropertyValue get(String propertyName) { if (propertyValues != null) { for (PropertyValue pv : propertyValues) { if (pv.getName().equals(propertyName)) { return pv; } } } return null; } /** * Return the changes since the previous PropertyValues. * Subclasses should also override {@code equals}. * * @param old the old property values * @return the updated or new properties. * Return empty PropertyValues if there are no changes. * @see Object#equals */ public PropertyValues changesSince(PropertyValues old) { PropertyValues changes = new PropertyValues(); if (old != this && propertyValues != null) { // for each property value in the new set for (PropertyValue newPv : propertyValues) { // if there wasn't an old one, add it PropertyValue pvOld = old.get(newPv.getName()); if (pvOld == null || !pvOld.equals(newPv)) { changes.add(newPv); } } } return changes; } /** * Is there a property value (or other processing entry) for this property? * * @param propertyName the name of the property we're interested in * @return whether there is a property value for this property */ public boolean contains(String propertyName) { return (propertyValues != null && getPropertyValue(propertyName) != null) || (processedProperties != null && processedProperties.contains(propertyName)); } /** * Does this holder not contain any PropertyValue objects at all? */ public boolean isEmpty() { return propertyValues == null || propertyValues.isEmpty(); } /** * 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 PropertyValues add(String propertyName, @Nullable Object propertyValue) { Assert.notNull(propertyName, "propertyName is required"); add(new PropertyValue(propertyName, propertyValue)); return this; } /** * Add a PropertyValue object, replacing any existing one for the * corresponding property or getting merged with it (if applicable). * * @param pvs the PropertyValue array object to add * @return this in order to allow for adding multiple property values in a chain */ public PropertyValues add(@Nullable PropertyValue... pvs) { if (ObjectUtils.isNotEmpty(pvs)) { ArrayList propertyValues = propertyValues(); outer: for (PropertyValue pv : pvs) { int i = 0; for (PropertyValue currentPv : propertyValues) { if (currentPv.getName().equals(pv.getName())) { pv = mergeIfRequired(pv, currentPv); setAt(pv, i); continue outer; } i++; } propertyValues.add(pv); } } return this; } /** * 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; } /** * 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 * @see Map#putAll(Map) */ public PropertyValues add(@Nullable Map other) { if (CollectionUtils.isNotEmpty(other)) { ArrayList propertyValues = propertyValues(); for (Map.Entry entry : other.entrySet()) { propertyValues.add(new PropertyValue(entry)); } } return this; } private ArrayList propertyValues() { if (this.propertyValues == null) { this.propertyValues = new ArrayList<>(); } return propertyValues; } /** * 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 * @see Map#putAll(Map) */ public PropertyValues add(@Nullable PropertyValues other) { if (other != null && CollectionUtils.isNotEmpty(other.propertyValues)) { for (PropertyValue pv : other.propertyValues) { add(new PropertyValue(pv)); } } return this; } /** * Clear and 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 * @see Map#putAll(Map) */ public PropertyValues set(PropertyValues other) { if (this.propertyValues == null) { if (other != null && CollectionUtils.isNotEmpty(other.propertyValues)) { this.propertyValues = new ArrayList<>(); add(other); } } else { this.propertyValues.clear(); add(other); } return this; } /** * Apply bean' {@link PropertyValue}s * * @param propertyValues The array of the bean's PropertyValue s */ public PropertyValues set(PropertyValue... propertyValues) { if (this.propertyValues == null) { if (ObjectUtils.isNotEmpty(propertyValues)) { this.propertyValues = new ArrayList<>(); add(propertyValues); } } else { this.propertyValues.clear(); add(propertyValues); } return this; } public PropertyValues set(@Nullable Collection propertyValues) { if (this.propertyValues != null) { this.propertyValues.clear(); } if (CollectionUtils.isNotEmpty(propertyValues)) { this.propertyValues = new ArrayList<>(propertyValues); } return this; } /** @since 4.0 */ public PropertyValues set(@Nullable Map propertyValues) { if (CollectionUtils.isEmpty(propertyValues)) { if (this.propertyValues != null) { this.propertyValues.clear(); } } else { if (this.propertyValues == null) { this.propertyValues = new ArrayList<>(); } else { this.propertyValues.clear(); } for (Map.Entry entry : propertyValues.entrySet()) { this.propertyValues.add(new PropertyValue(entry)); } } return this; } /** * Modify a PropertyValue object held in this object. * Indexed from 0. */ public void setAt(PropertyValue pv, int i) { if (propertyValues == null) { propertyValues = new ArrayList<>(); } propertyValues.set(i, pv); } public void clear() { if (propertyValues != null) { propertyValues.clear(); } } /** * Return the underlying map of property-values. The returned Map * can be modified directly, although this is not recommended. */ @Nullable public Map asMap() { if (CollectionUtils.isEmpty(propertyValues)) { return Collections.emptyMap(); } LinkedHashMap ret = new LinkedHashMap<>(); for (PropertyValue propertyValue : propertyValues) { ret.put(propertyValue.getName(), propertyValue.getValue()); } return ret; } public PropertyValue[] toArray() { if (CollectionUtils.isEmpty(propertyValues)) { return new PropertyValue[0]; } return propertyValues().toArray(new PropertyValue[0]); } /** * Return the underlying List of PropertyValue objects in its raw form. */ public List asList() { return propertyValues(); } @Override public Iterator iterator() { if (CollectionUtils.isEmpty(propertyValues)) { return Collections.emptyIterator(); } return propertyValues.iterator(); } @Override public Spliterator spliterator() { if (CollectionUtils.isEmpty(propertyValues)) { return Spliterators.emptySpliterator(); } return propertyValues.spliterator(); } public Stream stream() { if (CollectionUtils.isEmpty(propertyValues)) { return Stream.empty(); } return propertyValues.stream(); } /** * 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; } /** * Register the specified property as "processed" in the sense * of some processor calling the corresponding setter method * outside of 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 (processedProperties == null) { this.processedProperties = new HashSet<>(4); } processedProperties.add(propertyName); } /** * Clear the "processed" registration of the given property, if any. */ public void clearProcessedProperty(String propertyName) { if (processedProperties != null) { processedProperties.remove(propertyName); } } @Override public int hashCode() { return Objects.hashCode(propertyValues); } @Override public boolean equals(@Nullable Object other) { return this == other || (other instanceof PropertyValues values && Objects.equals(this.propertyValues, values.propertyValues)); } @Override public String toString() { PropertyValue[] pvs = toArray(); if (pvs.length > 0) { return "PropertyValues: length=%s; %s".formatted(pvs.length, StringUtils.arrayToDelimitedString(pvs, "; ")); } return "PropertyValues: length=0"; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy