001/*
002 * Copyright 2002-2019 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;
020
021import org.springframework.lang.Nullable;
022import org.springframework.util.Assert;
023import org.springframework.util.ObjectUtils;
024
025/**
026 * Object to hold information and value for an individual bean property.
027 * Using an object here, rather than just storing all properties in
028 * a map keyed by property name, allows for more flexibility, and the
029 * ability to handle indexed properties etc in an optimized way.
030 *
031 * <p>Note that the value doesn't need to be the final required type:
032 * A {@link BeanWrapper} implementation should handle any necessary conversion,
033 * as this object doesn't know anything about the objects it will be applied to.
034 *
035 * @author Rod Johnson
036 * @author Rob Harrop
037 * @author Juergen Hoeller
038 * @since 13 May 2001
039 * @see PropertyValues
040 * @see BeanWrapper
041 */
042@SuppressWarnings("serial")
043public class PropertyValue extends BeanMetadataAttributeAccessor implements Serializable {
044
045        private final String name;
046
047        @Nullable
048        private final Object value;
049
050        private boolean optional = false;
051
052        private boolean converted = false;
053
054        @Nullable
055        private Object convertedValue;
056
057        /** Package-visible field that indicates whether conversion is necessary. */
058        @Nullable
059        volatile Boolean conversionNecessary;
060
061        /** Package-visible field for caching the resolved property path tokens. */
062        @Nullable
063        transient volatile Object resolvedTokens;
064
065
066        /**
067         * Create a new PropertyValue instance.
068         * @param name the name of the property (never {@code null})
069         * @param value the value of the property (possibly before type conversion)
070         */
071        public PropertyValue(String name, @Nullable Object value) {
072                Assert.notNull(name, "Name must not be null");
073                this.name = name;
074                this.value = value;
075        }
076
077        /**
078         * Copy constructor.
079         * @param original the PropertyValue to copy (never {@code null})
080         */
081        public PropertyValue(PropertyValue original) {
082                Assert.notNull(original, "Original must not be null");
083                this.name = original.getName();
084                this.value = original.getValue();
085                this.optional = original.isOptional();
086                this.converted = original.converted;
087                this.convertedValue = original.convertedValue;
088                this.conversionNecessary = original.conversionNecessary;
089                this.resolvedTokens = original.resolvedTokens;
090                setSource(original.getSource());
091                copyAttributesFrom(original);
092        }
093
094        /**
095         * Constructor that exposes a new value for an original value holder.
096         * The original holder will be exposed as source of the new holder.
097         * @param original the PropertyValue to link to (never {@code null})
098         * @param newValue the new value to apply
099         */
100        public PropertyValue(PropertyValue original, @Nullable Object newValue) {
101                Assert.notNull(original, "Original must not be null");
102                this.name = original.getName();
103                this.value = newValue;
104                this.optional = original.isOptional();
105                this.conversionNecessary = original.conversionNecessary;
106                this.resolvedTokens = original.resolvedTokens;
107                setSource(original);
108                copyAttributesFrom(original);
109        }
110
111
112        /**
113         * Return the name of the property.
114         */
115        public String getName() {
116                return this.name;
117        }
118
119        /**
120         * Return the value of the property.
121         * <p>Note that type conversion will <i>not</i> have occurred here.
122         * It is the responsibility of the BeanWrapper implementation to
123         * perform type conversion.
124         */
125        @Nullable
126        public Object getValue() {
127                return this.value;
128        }
129
130        /**
131         * Return the original PropertyValue instance for this value holder.
132         * @return the original PropertyValue (either a source of this
133         * value holder or this value holder itself).
134         */
135        public PropertyValue getOriginalPropertyValue() {
136                PropertyValue original = this;
137                Object source = getSource();
138                while (source instanceof PropertyValue && source != original) {
139                        original = (PropertyValue) source;
140                        source = original.getSource();
141                }
142                return original;
143        }
144
145        /**
146         * Set whether this is an optional value, that is, to be ignored
147         * when no corresponding property exists on the target class.
148         * @since 3.0
149         */
150        public void setOptional(boolean optional) {
151                this.optional = optional;
152        }
153
154        /**
155         * Return whether this is an optional value, that is, to be ignored
156         * when no corresponding property exists on the target class.
157         * @since 3.0
158         */
159        public boolean isOptional() {
160                return this.optional;
161        }
162
163        /**
164         * Return whether this holder contains a converted value already ({@code true}),
165         * or whether the value still needs to be converted ({@code false}).
166         */
167        public synchronized boolean isConverted() {
168                return this.converted;
169        }
170
171        /**
172         * Set the converted value of this property value,
173         * after processed type conversion.
174         */
175        public synchronized void setConvertedValue(@Nullable Object value) {
176                this.converted = true;
177                this.convertedValue = value;
178        }
179
180        /**
181         * Return the converted value of this property value,
182         * after processed type conversion.
183         */
184        @Nullable
185        public synchronized Object getConvertedValue() {
186                return this.convertedValue;
187        }
188
189
190        @Override
191        public boolean equals(@Nullable Object other) {
192                if (this == other) {
193                        return true;
194                }
195                if (!(other instanceof PropertyValue)) {
196                        return false;
197                }
198                PropertyValue otherPv = (PropertyValue) other;
199                return (this.name.equals(otherPv.name) &&
200                                ObjectUtils.nullSafeEquals(this.value, otherPv.value) &&
201                                ObjectUtils.nullSafeEquals(getSource(), otherPv.getSource()));
202        }
203
204        @Override
205        public int hashCode() {
206                return this.name.hashCode() * 29 + ObjectUtils.nullSafeHashCode(this.value);
207        }
208
209        @Override
210        public String toString() {
211                return "bean property '" + this.name + "'";
212        }
213
214}