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