001/*
002 * Copyright 2002-2016 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.validation;
018
019import java.beans.PropertyEditor;
020
021import org.springframework.beans.BeanUtils;
022import org.springframework.beans.ConfigurablePropertyAccessor;
023import org.springframework.beans.PropertyAccessorUtils;
024import org.springframework.beans.PropertyEditorRegistry;
025import org.springframework.core.convert.ConversionService;
026import org.springframework.core.convert.TypeDescriptor;
027import org.springframework.core.convert.support.ConvertingPropertyEditorAdapter;
028import org.springframework.util.Assert;
029
030/**
031 * Abstract base class for {@link BindingResult} implementations that work with
032 * Spring's {@link org.springframework.beans.PropertyAccessor} mechanism.
033 * Pre-implements field access through delegation to the corresponding
034 * PropertyAccessor methods.
035 *
036 * @author Juergen Hoeller
037 * @since 2.0
038 * @see #getPropertyAccessor()
039 * @see org.springframework.beans.PropertyAccessor
040 * @see org.springframework.beans.ConfigurablePropertyAccessor
041 */
042@SuppressWarnings("serial")
043public abstract class AbstractPropertyBindingResult extends AbstractBindingResult {
044
045        private transient ConversionService conversionService;
046
047
048        /**
049         * Create a new AbstractPropertyBindingResult instance.
050         * @param objectName the name of the target object
051         * @see DefaultMessageCodesResolver
052         */
053        protected AbstractPropertyBindingResult(String objectName) {
054                super(objectName);
055        }
056
057
058        public void initConversion(ConversionService conversionService) {
059                Assert.notNull(conversionService, "ConversionService must not be null");
060                this.conversionService = conversionService;
061                if (getTarget() != null) {
062                        getPropertyAccessor().setConversionService(conversionService);
063                }
064        }
065
066        /**
067         * Returns the underlying PropertyAccessor.
068         * @see #getPropertyAccessor()
069         */
070        @Override
071        public PropertyEditorRegistry getPropertyEditorRegistry() {
072                return getPropertyAccessor();
073        }
074
075        /**
076         * Returns the canonical property name.
077         * @see org.springframework.beans.PropertyAccessorUtils#canonicalPropertyName
078         */
079        @Override
080        protected String canonicalFieldName(String field) {
081                return PropertyAccessorUtils.canonicalPropertyName(field);
082        }
083
084        /**
085         * Determines the field type from the property type.
086         * @see #getPropertyAccessor()
087         */
088        @Override
089        public Class<?> getFieldType(String field) {
090                return getPropertyAccessor().getPropertyType(fixedField(field));
091        }
092
093        /**
094         * Fetches the field value from the PropertyAccessor.
095         * @see #getPropertyAccessor()
096         */
097        @Override
098        protected Object getActualFieldValue(String field) {
099                return getPropertyAccessor().getPropertyValue(field);
100        }
101
102        /**
103         * Formats the field value based on registered PropertyEditors.
104         * @see #getCustomEditor
105         */
106        @Override
107        protected Object formatFieldValue(String field, Object value) {
108                String fixedField = fixedField(field);
109                // Try custom editor...
110                PropertyEditor customEditor = getCustomEditor(fixedField);
111                if (customEditor != null) {
112                        customEditor.setValue(value);
113                        String textValue = customEditor.getAsText();
114                        // If the PropertyEditor returned null, there is no appropriate
115                        // text representation for this value: only use it if non-null.
116                        if (textValue != null) {
117                                return textValue;
118                        }
119                }
120                if (this.conversionService != null) {
121                        // Try custom converter...
122                        TypeDescriptor fieldDesc = getPropertyAccessor().getPropertyTypeDescriptor(fixedField);
123                        TypeDescriptor strDesc = TypeDescriptor.valueOf(String.class);
124                        if (fieldDesc != null && this.conversionService.canConvert(fieldDesc, strDesc)) {
125                                return this.conversionService.convert(value, fieldDesc, strDesc);
126                        }
127                }
128                return value;
129        }
130
131        /**
132         * Retrieve the custom PropertyEditor for the given field, if any.
133         * @param fixedField the fully qualified field name
134         * @return the custom PropertyEditor, or {@code null}
135         */
136        protected PropertyEditor getCustomEditor(String fixedField) {
137                Class<?> targetType = getPropertyAccessor().getPropertyType(fixedField);
138                PropertyEditor editor = getPropertyAccessor().findCustomEditor(targetType, fixedField);
139                if (editor == null) {
140                        editor = BeanUtils.findEditorByConvention(targetType);
141                }
142                return editor;
143        }
144
145        /**
146         * This implementation exposes a PropertyEditor adapter for a Formatter,
147         * if applicable.
148         */
149        @Override
150        public PropertyEditor findEditor(String field, Class<?> valueType) {
151                Class<?> valueTypeForLookup = valueType;
152                if (valueTypeForLookup == null) {
153                        valueTypeForLookup = getFieldType(field);
154                }
155                PropertyEditor editor = super.findEditor(field, valueTypeForLookup);
156                if (editor == null && this.conversionService != null) {
157                        TypeDescriptor td = null;
158                        if (field != null) {
159                                TypeDescriptor ptd = getPropertyAccessor().getPropertyTypeDescriptor(fixedField(field));
160                                if (valueType == null || valueType.isAssignableFrom(ptd.getType())) {
161                                        td = ptd;
162                                }
163                        }
164                        if (td == null) {
165                                td = TypeDescriptor.valueOf(valueTypeForLookup);
166                        }
167                        if (this.conversionService.canConvert(TypeDescriptor.valueOf(String.class), td)) {
168                                editor = new ConvertingPropertyEditorAdapter(this.conversionService, td);
169                        }
170                }
171                return editor;
172        }
173
174
175        /**
176         * Provide the PropertyAccessor to work with, according to the
177         * concrete strategy of access.
178         * <p>Note that a PropertyAccessor used by a BindingResult should
179         * always have its "extractOldValueForEditor" flag set to "true"
180         * by default, since this is typically possible without side effects
181         * for model objects that serve as data binding target.
182         * @see ConfigurablePropertyAccessor#setExtractOldValueForEditor
183         */
184        public abstract ConfigurablePropertyAccessor getPropertyAccessor();
185
186}