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