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