001/*
002 * Copyright 2002-2020 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;
018
019import java.lang.reflect.Field;
020import java.util.HashMap;
021import java.util.Map;
022
023import org.springframework.core.ResolvableType;
024import org.springframework.core.convert.TypeDescriptor;
025import org.springframework.util.ReflectionUtils;
026
027/**
028 * {@link ConfigurablePropertyAccessor} implementation that directly accesses
029 * instance fields. Allows for direct binding to fields instead of going through
030 * JavaBean setters.
031 *
032 * <p>As of Spring 4.2, the vast majority of the {@link BeanWrapper} features have
033 * been merged to {@link AbstractPropertyAccessor}, which means that property
034 * traversal as well as collections and map access is now supported here as well.
035 *
036 * <p>A DirectFieldAccessor's default for the "extractOldValueForEditor" setting
037 * is "true", since a field can always be read without side effects.
038 *
039 * @author Juergen Hoeller
040 * @author Stephane Nicoll
041 * @since 2.0
042 * @see #setExtractOldValueForEditor
043 * @see BeanWrapper
044 * @see org.springframework.validation.DirectFieldBindingResult
045 * @see org.springframework.validation.DataBinder#initDirectFieldAccess()
046 */
047public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
048
049        private final Map<String, FieldPropertyHandler> fieldMap = new HashMap<String, FieldPropertyHandler>();
050
051
052        /**
053         * Create a new DirectFieldAccessor for the given object.
054         * @param object the object wrapped by this DirectFieldAccessor
055         */
056        public DirectFieldAccessor(Object object) {
057                super(object);
058        }
059
060        /**
061         * Create a new DirectFieldAccessor for the given object,
062         * registering a nested path that the object is in.
063         * @param object the object wrapped by this DirectFieldAccessor
064         * @param nestedPath the nested path of the object
065         * @param parent the containing DirectFieldAccessor (must not be {@code null})
066         */
067        protected DirectFieldAccessor(Object object, String nestedPath, DirectFieldAccessor parent) {
068                super(object, nestedPath, parent);
069        }
070
071
072        @Override
073        protected FieldPropertyHandler getLocalPropertyHandler(String propertyName) {
074                FieldPropertyHandler propertyHandler = this.fieldMap.get(propertyName);
075                if (propertyHandler == null) {
076                        Field field = ReflectionUtils.findField(getWrappedClass(), propertyName);
077                        if (field != null) {
078                                propertyHandler = new FieldPropertyHandler(field);
079                                this.fieldMap.put(propertyName, propertyHandler);
080                        }
081                }
082                return propertyHandler;
083        }
084
085        @Override
086        protected DirectFieldAccessor newNestedPropertyAccessor(Object object, String nestedPath) {
087                return new DirectFieldAccessor(object, nestedPath, this);
088        }
089
090        @Override
091        protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) {
092                PropertyMatches matches = PropertyMatches.forField(propertyName, getRootClass());
093                throw new NotWritablePropertyException(getRootClass(), getNestedPath() + propertyName,
094                                matches.buildErrorMessage(), matches.getPossibleMatches());
095        }
096
097
098        private class FieldPropertyHandler extends PropertyHandler {
099
100                private final Field field;
101
102                public FieldPropertyHandler(Field field) {
103                        super(field.getType(), true, true);
104                        this.field = field;
105                }
106
107                @Override
108                public TypeDescriptor toTypeDescriptor() {
109                        return new TypeDescriptor(this.field);
110                }
111
112                @Override
113                public ResolvableType getResolvableType() {
114                        return ResolvableType.forField(this.field);
115                }
116
117                @Override
118                public TypeDescriptor nested(int level) {
119                        return TypeDescriptor.nested(this.field, level);
120                }
121
122                @Override
123                public Object getValue() throws Exception {
124                        try {
125                                ReflectionUtils.makeAccessible(this.field);
126                                return this.field.get(getWrappedInstance());
127                        }
128
129                        catch (IllegalAccessException ex) {
130                                throw new InvalidPropertyException(getWrappedClass(),
131                                                this.field.getName(), "Field is not accessible", ex);
132                        }
133                }
134
135                @Override
136                public void setValue(Object object, Object value) throws Exception {
137                        try {
138                                ReflectionUtils.makeAccessible(this.field);
139                                this.field.set(object, value);
140                        }
141                        catch (IllegalAccessException ex) {
142                                throw new InvalidPropertyException(getWrappedClass(), this.field.getName(),
143                                                "Field is not accessible", ex);
144                        }
145                }
146        }
147
148}