001/*
002 * Copyright 2002-2011 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 java.util.LinkedHashMap;
020import java.util.LinkedHashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024
025import org.springframework.beans.MutablePropertyValues;
026import org.springframework.beans.PropertyValue;
027import org.springframework.util.Assert;
028import org.springframework.util.ObjectUtils;
029import org.springframework.util.StringValueResolver;
030
031/**
032 * Visitor class for traversing {@link BeanDefinition} objects, in particular
033 * the property values and constructor argument values contained in them,
034 * resolving bean metadata values.
035 *
036 * <p>Used by {@link PropertyPlaceholderConfigurer} to parse all String values
037 * contained in a BeanDefinition, resolving any placeholders found.
038 *
039 * @author Juergen Hoeller
040 * @author Sam Brannen
041 * @since 1.2
042 * @see BeanDefinition
043 * @see BeanDefinition#getPropertyValues
044 * @see BeanDefinition#getConstructorArgumentValues
045 * @see PropertyPlaceholderConfigurer
046 */
047public class BeanDefinitionVisitor {
048
049        private StringValueResolver valueResolver;
050
051
052        /**
053         * Create a new BeanDefinitionVisitor, applying the specified
054         * value resolver to all bean metadata values.
055         * @param valueResolver the StringValueResolver to apply
056         */
057        public BeanDefinitionVisitor(StringValueResolver valueResolver) {
058                Assert.notNull(valueResolver, "StringValueResolver must not be null");
059                this.valueResolver = valueResolver;
060        }
061
062        /**
063         * Create a new BeanDefinitionVisitor for subclassing.
064         * Subclasses need to override the {@link #resolveStringValue} method.
065         */
066        protected BeanDefinitionVisitor() {
067        }
068
069
070        /**
071         * Traverse the given BeanDefinition object and the MutablePropertyValues
072         * and ConstructorArgumentValues contained in them.
073         * @param beanDefinition the BeanDefinition object to traverse
074         * @see #resolveStringValue(String)
075         */
076        public void visitBeanDefinition(BeanDefinition beanDefinition) {
077                visitParentName(beanDefinition);
078                visitBeanClassName(beanDefinition);
079                visitFactoryBeanName(beanDefinition);
080                visitFactoryMethodName(beanDefinition);
081                visitScope(beanDefinition);
082                visitPropertyValues(beanDefinition.getPropertyValues());
083                ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
084                visitIndexedArgumentValues(cas.getIndexedArgumentValues());
085                visitGenericArgumentValues(cas.getGenericArgumentValues());
086        }
087
088        protected void visitParentName(BeanDefinition beanDefinition) {
089                String parentName = beanDefinition.getParentName();
090                if (parentName != null) {
091                        String resolvedName = resolveStringValue(parentName);
092                        if (!parentName.equals(resolvedName)) {
093                                beanDefinition.setParentName(resolvedName);
094                        }
095                }
096        }
097
098        protected void visitBeanClassName(BeanDefinition beanDefinition) {
099                String beanClassName = beanDefinition.getBeanClassName();
100                if (beanClassName != null) {
101                        String resolvedName = resolveStringValue(beanClassName);
102                        if (!beanClassName.equals(resolvedName)) {
103                                beanDefinition.setBeanClassName(resolvedName);
104                        }
105                }
106        }
107
108        protected void visitFactoryBeanName(BeanDefinition beanDefinition) {
109                String factoryBeanName = beanDefinition.getFactoryBeanName();
110                if (factoryBeanName != null) {
111                        String resolvedName = resolveStringValue(factoryBeanName);
112                        if (!factoryBeanName.equals(resolvedName)) {
113                                beanDefinition.setFactoryBeanName(resolvedName);
114                        }
115                }
116        }
117
118        protected void visitFactoryMethodName(BeanDefinition beanDefinition) {
119                String factoryMethodName = beanDefinition.getFactoryMethodName();
120                if (factoryMethodName != null) {
121                        String resolvedName = resolveStringValue(factoryMethodName);
122                        if (!factoryMethodName.equals(resolvedName)) {
123                                beanDefinition.setFactoryMethodName(resolvedName);
124                        }
125                }
126        }
127
128        protected void visitScope(BeanDefinition beanDefinition) {
129                String scope = beanDefinition.getScope();
130                if (scope != null) {
131                        String resolvedScope = resolveStringValue(scope);
132                        if (!scope.equals(resolvedScope)) {
133                                beanDefinition.setScope(resolvedScope);
134                        }
135                }
136        }
137
138        protected void visitPropertyValues(MutablePropertyValues pvs) {
139                PropertyValue[] pvArray = pvs.getPropertyValues();
140                for (PropertyValue pv : pvArray) {
141                        Object newVal = resolveValue(pv.getValue());
142                        if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
143                                pvs.add(pv.getName(), newVal);
144                        }
145                }
146        }
147
148        protected void visitIndexedArgumentValues(Map<Integer, ConstructorArgumentValues.ValueHolder> ias) {
149                for (ConstructorArgumentValues.ValueHolder valueHolder : ias.values()) {
150                        Object newVal = resolveValue(valueHolder.getValue());
151                        if (!ObjectUtils.nullSafeEquals(newVal, valueHolder.getValue())) {
152                                valueHolder.setValue(newVal);
153                        }
154                }
155        }
156
157        protected void visitGenericArgumentValues(List<ConstructorArgumentValues.ValueHolder> gas) {
158                for (ConstructorArgumentValues.ValueHolder valueHolder : gas) {
159                        Object newVal = resolveValue(valueHolder.getValue());
160                        if (!ObjectUtils.nullSafeEquals(newVal, valueHolder.getValue())) {
161                                valueHolder.setValue(newVal);
162                        }
163                }
164        }
165
166        @SuppressWarnings("rawtypes")
167        protected Object resolveValue(Object value) {
168                if (value instanceof BeanDefinition) {
169                        visitBeanDefinition((BeanDefinition) value);
170                }
171                else if (value instanceof BeanDefinitionHolder) {
172                        visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
173                }
174                else if (value instanceof RuntimeBeanReference) {
175                        RuntimeBeanReference ref = (RuntimeBeanReference) value;
176                        String newBeanName = resolveStringValue(ref.getBeanName());
177                        if (!newBeanName.equals(ref.getBeanName())) {
178                                return new RuntimeBeanReference(newBeanName);
179                        }
180                }
181                else if (value instanceof RuntimeBeanNameReference) {
182                        RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value;
183                        String newBeanName = resolveStringValue(ref.getBeanName());
184                        if (!newBeanName.equals(ref.getBeanName())) {
185                                return new RuntimeBeanNameReference(newBeanName);
186                        }
187                }
188                else if (value instanceof Object[]) {
189                        visitArray((Object[]) value);
190                }
191                else if (value instanceof List) {
192                        visitList((List) value);
193                }
194                else if (value instanceof Set) {
195                        visitSet((Set) value);
196                }
197                else if (value instanceof Map) {
198                        visitMap((Map) value);
199                }
200                else if (value instanceof TypedStringValue) {
201                        TypedStringValue typedStringValue = (TypedStringValue) value;
202                        String stringValue = typedStringValue.getValue();
203                        if (stringValue != null) {
204                                String visitedString = resolveStringValue(stringValue);
205                                typedStringValue.setValue(visitedString);
206                        }
207                }
208                else if (value instanceof String) {
209                        return resolveStringValue((String) value);
210                }
211                return value;
212        }
213
214        protected void visitArray(Object[] arrayVal) {
215                for (int i = 0; i < arrayVal.length; i++) {
216                        Object elem = arrayVal[i];
217                        Object newVal = resolveValue(elem);
218                        if (!ObjectUtils.nullSafeEquals(newVal, elem)) {
219                                arrayVal[i] = newVal;
220                        }
221                }
222        }
223
224        @SuppressWarnings({"unchecked", "rawtypes"})
225        protected void visitList(List listVal) {
226                for (int i = 0; i < listVal.size(); i++) {
227                        Object elem = listVal.get(i);
228                        Object newVal = resolveValue(elem);
229                        if (!ObjectUtils.nullSafeEquals(newVal, elem)) {
230                                listVal.set(i, newVal);
231                        }
232                }
233        }
234
235        @SuppressWarnings({"unchecked", "rawtypes"})
236        protected void visitSet(Set setVal) {
237                Set newContent = new LinkedHashSet();
238                boolean entriesModified = false;
239                for (Object elem : setVal) {
240                        int elemHash = (elem != null ? elem.hashCode() : 0);
241                        Object newVal = resolveValue(elem);
242                        int newValHash = (newVal != null ? newVal.hashCode() : 0);
243                        newContent.add(newVal);
244                        entriesModified = entriesModified || (newVal != elem || newValHash != elemHash);
245                }
246                if (entriesModified) {
247                        setVal.clear();
248                        setVal.addAll(newContent);
249                }
250        }
251
252        @SuppressWarnings({"unchecked", "rawtypes"})
253        protected void visitMap(Map<?, ?> mapVal) {
254                Map newContent = new LinkedHashMap();
255                boolean entriesModified = false;
256                for (Map.Entry entry : mapVal.entrySet()) {
257                        Object key = entry.getKey();
258                        int keyHash = (key != null ? key.hashCode() : 0);
259                        Object newKey = resolveValue(key);
260                        int newKeyHash = (newKey != null ? newKey.hashCode() : 0);
261                        Object val = entry.getValue();
262                        Object newVal = resolveValue(val);
263                        newContent.put(newKey, newVal);
264                        entriesModified = entriesModified || (newVal != val || newKey != key || newKeyHash != keyHash);
265                }
266                if (entriesModified) {
267                        mapVal.clear();
268                        mapVal.putAll(newContent);
269                }
270        }
271
272        /**
273         * Resolve the given String value, for example parsing placeholders.
274         * @param strVal the original String value
275         * @return the resolved String value
276         */
277        protected String resolveStringValue(String strVal) {
278                if (this.valueResolver == null) {
279                        throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
280                                        "object into the constructor or override the 'resolveStringValue' method");
281                }
282                String resolvedValue = this.valueResolver.resolveStringValue(strVal);
283                // Return original String if not modified.
284                return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
285        }
286
287}