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.factory.config;
018
019import org.springframework.beans.BeanMetadataElement;
020import org.springframework.lang.Nullable;
021import org.springframework.util.Assert;
022import org.springframework.util.ClassUtils;
023import org.springframework.util.ObjectUtils;
024
025/**
026 * Holder for a typed String value. Can be added to bean definitions
027 * in order to explicitly specify a target type for a String value,
028 * for example for collection elements.
029 *
030 * <p>This holder will just store the String value and the target type.
031 * The actual conversion will be performed by the bean factory.
032 *
033 * @author Juergen Hoeller
034 * @since 1.2
035 * @see BeanDefinition#getPropertyValues
036 * @see org.springframework.beans.MutablePropertyValues#addPropertyValue
037 */
038public class TypedStringValue implements BeanMetadataElement {
039
040        @Nullable
041        private String value;
042
043        @Nullable
044        private volatile Object targetType;
045
046        @Nullable
047        private Object source;
048
049        @Nullable
050        private String specifiedTypeName;
051
052        private volatile boolean dynamic;
053
054
055        /**
056         * Create a new {@link TypedStringValue} for the given String value.
057         * @param value the String value
058         */
059        public TypedStringValue(@Nullable String value) {
060                setValue(value);
061        }
062
063        /**
064         * Create a new {@link TypedStringValue} for the given String value
065         * and target type.
066         * @param value the String value
067         * @param targetType the type to convert to
068         */
069        public TypedStringValue(@Nullable String value, Class<?> targetType) {
070                setValue(value);
071                setTargetType(targetType);
072        }
073
074        /**
075         * Create a new {@link TypedStringValue} for the given String value
076         * and target type.
077         * @param value the String value
078         * @param targetTypeName the type to convert to
079         */
080        public TypedStringValue(@Nullable String value, String targetTypeName) {
081                setValue(value);
082                setTargetTypeName(targetTypeName);
083        }
084
085
086        /**
087         * Set the String value.
088         * <p>Only necessary for manipulating a registered value,
089         * for example in BeanFactoryPostProcessors.
090         */
091        public void setValue(@Nullable String value) {
092                this.value = value;
093        }
094
095        /**
096         * Return the String value.
097         */
098        @Nullable
099        public String getValue() {
100                return this.value;
101        }
102
103        /**
104         * Set the type to convert to.
105         * <p>Only necessary for manipulating a registered value,
106         * for example in BeanFactoryPostProcessors.
107         */
108        public void setTargetType(Class<?> targetType) {
109                Assert.notNull(targetType, "'targetType' must not be null");
110                this.targetType = targetType;
111        }
112
113        /**
114         * Return the type to convert to.
115         */
116        public Class<?> getTargetType() {
117                Object targetTypeValue = this.targetType;
118                if (!(targetTypeValue instanceof Class)) {
119                        throw new IllegalStateException("Typed String value does not carry a resolved target type");
120                }
121                return (Class<?>) targetTypeValue;
122        }
123
124        /**
125         * Specify the type to convert to.
126         */
127        public void setTargetTypeName(@Nullable String targetTypeName) {
128                this.targetType = targetTypeName;
129        }
130
131        /**
132         * Return the type to convert to.
133         */
134        @Nullable
135        public String getTargetTypeName() {
136                Object targetTypeValue = this.targetType;
137                if (targetTypeValue instanceof Class) {
138                        return ((Class<?>) targetTypeValue).getName();
139                }
140                else {
141                        return (String) targetTypeValue;
142                }
143        }
144
145        /**
146         * Return whether this typed String value carries a target type .
147         */
148        public boolean hasTargetType() {
149                return (this.targetType instanceof Class);
150        }
151
152        /**
153         * Determine the type to convert to, resolving it from a specified class name
154         * if necessary. Will also reload a specified Class from its name when called
155         * with the target type already resolved.
156         * @param classLoader the ClassLoader to use for resolving a (potential) class name
157         * @return the resolved type to convert to
158         * @throws ClassNotFoundException if the type cannot be resolved
159         */
160        @Nullable
161        public Class<?> resolveTargetType(@Nullable ClassLoader classLoader) throws ClassNotFoundException {
162                String typeName = getTargetTypeName();
163                if (typeName == null) {
164                        return null;
165                }
166                Class<?> resolvedClass = ClassUtils.forName(typeName, classLoader);
167                this.targetType = resolvedClass;
168                return resolvedClass;
169        }
170
171
172        /**
173         * Set the configuration source {@code Object} for this metadata element.
174         * <p>The exact type of the object will depend on the configuration mechanism used.
175         */
176        public void setSource(@Nullable Object source) {
177                this.source = source;
178        }
179
180        @Override
181        @Nullable
182        public Object getSource() {
183                return this.source;
184        }
185
186        /**
187         * Set the type name as actually specified for this particular value, if any.
188         */
189        public void setSpecifiedTypeName(@Nullable String specifiedTypeName) {
190                this.specifiedTypeName = specifiedTypeName;
191        }
192
193        /**
194         * Return the type name as actually specified for this particular value, if any.
195         */
196        @Nullable
197        public String getSpecifiedTypeName() {
198                return this.specifiedTypeName;
199        }
200
201        /**
202         * Mark this value as dynamic, i.e. as containing an expression
203         * and hence not being subject to caching.
204         */
205        public void setDynamic() {
206                this.dynamic = true;
207        }
208
209        /**
210         * Return whether this value has been marked as dynamic.
211         */
212        public boolean isDynamic() {
213                return this.dynamic;
214        }
215
216
217        @Override
218        public boolean equals(@Nullable Object other) {
219                if (this == other) {
220                        return true;
221                }
222                if (!(other instanceof TypedStringValue)) {
223                        return false;
224                }
225                TypedStringValue otherValue = (TypedStringValue) other;
226                return (ObjectUtils.nullSafeEquals(this.value, otherValue.value) &&
227                                ObjectUtils.nullSafeEquals(this.targetType, otherValue.targetType));
228        }
229
230        @Override
231        public int hashCode() {
232                return ObjectUtils.nullSafeHashCode(this.value) * 29 + ObjectUtils.nullSafeHashCode(this.targetType);
233        }
234
235        @Override
236        public String toString() {
237                return "TypedStringValue: value [" + this.value + "], target type [" + this.targetType + "]";
238        }
239
240}