
infra.beans.BeanWrapperImpl Maven / Gradle / Ivy
/*
* Copyright 2017 - 2024 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see [https://www.gnu.org/licenses/]
*/
package infra.beans;
import java.util.List;
import infra.core.ResolvableType;
import infra.core.TypeDescriptor;
import infra.lang.Nullable;
/**
* Default {@link BeanWrapper} implementation that should be sufficient
* for all typical use cases. Caches introspection results for efficiency.
*
* Note: Auto-registers default property editors from the
* {@code infra.beans.propertyeditors} package, which apply
* in addition to the JDK's standard PropertyEditors. Applications can call
* the {@link #registerCustomEditor(Class, java.beans.PropertyEditor)} method
* to register an editor for a particular instance (i.e. they are not shared
* across the application). See the base class
* {@link PropertyEditorRegistrySupport} for details.
*
*
NOTE: this is - for almost all purposes - an
* internal class. It is just public in order to allow for access from
* other framework packages. For standard application access purposes, use the
* {@link BeanWrapper#forBeanPropertyAccess} factory method instead.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @author Stephane Nicoll
* @author Harry Yang
* @see #registerCustomEditor
* @see #setPropertyValues
* @see #setPropertyValue
* @see #getPropertyValue
* @see #getPropertyType
* @see BeanWrapper
* @see PropertyEditorRegistrySupport
* @since 4.0 2022/2/17 17:40
*/
public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper {
@Nullable
private BeanMetadata beanMetadata;
/**
* Create a new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
* Registers default editors.
*
* @see #setWrappedInstance
*/
public BeanWrapperImpl() {
this(true);
}
/**
* Create a new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
*
* @param registerDefaultEditors whether to register default editors
* (can be suppressed if the BeanWrapper won't need any type conversion)
* @see #setWrappedInstance
*/
public BeanWrapperImpl(boolean registerDefaultEditors) {
super(registerDefaultEditors);
}
/**
* Create a new BeanWrapperImpl for the given object.
*
* @param object the object wrapped by this BeanWrapper
*/
public BeanWrapperImpl(Object object) {
super(object);
}
/**
* Create a new BeanWrapperImpl for the given object.
*
* @param object the object wrapped by this BeanWrapper
* @param beanMetadata the object beanMetadata
*/
public BeanWrapperImpl(Object object, @Nullable BeanMetadata beanMetadata) {
super(object);
registerDefaultEditors();
this.beanMetadata = beanMetadata;
}
/**
* Create a new BeanWrapperImpl, wrapping a new instance of the specified class.
*
* @param clazz class to instantiate and wrap
*/
public BeanWrapperImpl(Class> clazz) {
super(clazz);
}
/**
* Create a new BeanWrapperImpl for the given object,
* registering a nested path that the object is in.
*
* @param object the object wrapped by this BeanWrapper
* @param nestedPath the nested path of the object
* @param rootObject the root object at the top of the path
*/
public BeanWrapperImpl(Object object, String nestedPath, Object rootObject) {
super(object, nestedPath, rootObject);
}
/**
* Create a new BeanWrapperImpl for the given object,
* registering a nested path that the object is in.
*
* @param object the object wrapped by this BeanWrapper
* @param nestedPath the nested path of the object
* @param parent the containing BeanWrapper (must not be {@code null})
*/
private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl parent) {
super(object, nestedPath, parent);
}
/**
* Set a bean instance to hold, without any unwrapping of {@link java.util.Optional}.
*
* @param object the actual target object
* @see #setWrappedInstance(Object)
*/
public void setBeanInstance(Object object) {
this.rootObject = object;
this.wrappedObject = object;
this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
setIntrospectionClass(object.getClass());
}
@Override
public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {
super.setWrappedInstance(object, nestedPath, rootObject);
setIntrospectionClass(getWrappedClass());
}
/**
* Set the class to introspect.
* Needs to be called when the target object changes.
*
* @param clazz the class to introspect
*/
protected void setIntrospectionClass(Class> clazz) {
BeanMetadata beanMetadata = this.beanMetadata;
if (beanMetadata != null && beanMetadata.getType() != clazz) {
this.beanMetadata = null;
}
}
/**
* Obtain a lazily initialized BeanMetadata instance
* for the wrapped object.
*/
@Override
public BeanMetadata getMetadata() {
BeanMetadata beanMetadata = this.beanMetadata;
if (beanMetadata == null) {
beanMetadata = BeanMetadata.forClass(getWrappedClass());
this.beanMetadata = beanMetadata;
}
return beanMetadata;
}
/**
* Convert the given value for the specified property to the latter's type.
*
This method is only intended for optimizations in a BeanFactory.
* Use the {@code convertIfNecessary} methods for programmatic conversion.
*
* @param value the value to convert
* @param propertyName the target property
* (note that nested or indexed properties are not supported here)
* @return the new value, possibly the result of type conversion
* @throws TypeMismatchException if type conversion failed
*/
@Nullable
public Object convertForProperty(@Nullable Object value, String propertyName) throws TypeMismatchException {
BeanProperty beanProperty = getMetadata().getBeanProperty(propertyName);
if (beanProperty == null) {
throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
"No property '%s' found".formatted(propertyName));
}
TypeDescriptor typeDescriptor = beanProperty.getTypeDescriptor();
return convertForProperty(propertyName, null, value, typeDescriptor);
}
@Override
@Nullable
protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
BeanProperty beanProperty = getMetadata().getBeanProperty(propertyName);
return beanProperty != null ? new BeanPropertyHandler(beanProperty) : null;
}
@Override
protected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nestedPath) {
return new BeanWrapperImpl(object, nestedPath, this);
}
@Override
protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) {
PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass());
throw new NotWritablePropertyException(getRootClass(), getNestedPath() + propertyName,
matches.buildErrorMessage(), matches.getPossibleMatches());
}
@Override
public List getBeanProperties() {
return getMetadata().beanProperties();
}
@Override
public BeanProperty getBeanProperty(String propertyName) throws InvalidPropertyException {
BeanWrapperImpl nestedBw = (BeanWrapperImpl) getPropertyAccessorForPropertyPath(propertyName);
String finalPath = getFinalPath(nestedBw, propertyName);
BeanProperty property = nestedBw.getMetadata().getBeanProperty(finalPath);
if (property == null) {
throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
"No property '%s' found".formatted(propertyName));
}
return property;
}
private class BeanPropertyHandler extends PropertyHandler {
private final BeanProperty property;
public BeanPropertyHandler(BeanProperty property) {
super(property.getType(), property.isReadable(), property.isWriteable());
this.property = property;
}
@Override
public ResolvableType getResolvableType() {
return property.getResolvableType();
}
@Override
public TypeDescriptor toTypeDescriptor() {
return property.getTypeDescriptor();
}
@Override
public TypeDescriptor getMapValueType(int nestingLevel) {
TypeDescriptor typeDescriptor = property.getTypeDescriptor();
return new TypeDescriptor(
typeDescriptor.getResolvableType().getNested(nestingLevel).asMap().getGeneric(1),
null, property);
}
@Override
public TypeDescriptor getCollectionType(int nestingLevel) {
TypeDescriptor typeDescriptor = property.getTypeDescriptor();
return new TypeDescriptor(
typeDescriptor.getResolvableType().getNested(nestingLevel).asCollection().getGeneric(), null, property);
}
@Override
@Nullable
public TypeDescriptor nested(int level) {
return property.getTypeDescriptor().nested(level);
}
@Override
@Nullable
public Object getValue() throws Exception {
return property.getValue(getWrappedInstance());
}
@Override
public void setValue(@Nullable Object value) throws Exception {
property.setDirectly(getWrappedInstance(), value);
}
}
}