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