📄 beanwrapper.java
字号:
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.easyjf.beans;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import com.easyjf.beans.exception.BeansException;
import com.easyjf.beans.exception.PropertyException;
import com.easyjf.beans.exception.MethodInvocationException;
import com.easyjf.beans.exception.NotReadablePropertyException;
import com.easyjf.beans.exception.NotWritablePropertyException;
import com.easyjf.beans.exception.NullValueInNestedPathException;
import com.easyjf.beans.exception.TypeMismatchException;
import com.easyjf.util.Assert;
import com.easyjf.util.JdkVersion;
import com.easyjf.util.StringUtils;
public class BeanWrapper extends AbstractPropertyAccessor {
/**
* We'll create a lot of these objects, so we don't want a new logger every time.
*/
private final static Logger logger = Logger.getLogger(BeanWrapper.class);
/** The wrapped object */
private Object object;
private String nestedPath = "";
private Object rootObject;
private TypeConverter typeConverterDelegate;
/**
* Cached introspections results for this object, to prevent encountering
* the cost of JavaBeans introspection every time.
*/
private CachedIntrospectionResults cachedIntrospectionResults;
/**
* Map with cached nested BeanWrappers: nested path -> BeanWrapper instance.
*/
private Map nestedBeanWrappers;
/**
* Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
* Registers default editors.
* @see #setWrappedInstance
*/
public BeanWrapper() {
this(true);
}
/**
* Create 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 BeanWrapper(boolean registerDefaultEditors) {
if (registerDefaultEditors) {
registerDefaultEditors();
}
this.typeConverterDelegate = new TypeConverter(this);
}
/**
* Create new BeanWrapperImpl for the given object.
* @param object object wrapped by this BeanWrapper
*/
public BeanWrapper(Object object) {
registerDefaultEditors();
setWrappedInstance(object);
}
/**
* Create new BeanWrapperImpl, wrapping a new instance of the specified class.
* @param clazz class to instantiate and wrap
*/
public BeanWrapper(Class clazz) {
registerDefaultEditors();
setWrappedInstance(BeanUtils.instantiateClass(clazz));
}
/**
* Create new BeanWrapperImpl for the given object,
* registering a nested path that the object is in.
* @param object 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 BeanWrapper(Object object, String nestedPath, Object rootObject) {
registerDefaultEditors();
setWrappedInstance(object, nestedPath, rootObject);
}
/**
* Create new BeanWrapperImpl for the given object,
* registering a nested path that the object is in.
* @param object object wrapped by this BeanWrapper
* @param nestedPath the nested path of the object
* @param superBw the containing BeanWrapper (must not be <code>null</code>)
*/
private BeanWrapper(Object object, String nestedPath, BeanWrapper superBw) {
setWrappedInstance(object, nestedPath, superBw.getWrappedInstance());
//setExtractOldValueForEditor(superBw.isExtractOldValueForEditor());
}
//---------------------------------------------------------------------
// Implementation of BeanWrapper interface
//---------------------------------------------------------------------
/**
* Switch the target object, replacing the cached introspection results only
* if the class of the new object is different to that of the replaced object.
* @param object new target
*/
public void setWrappedInstance(Object object) {
setWrappedInstance(object, "", null);
}
/**
* Switch the target object, replacing the cached introspection results only
* if the class of the new object is different to that of the replaced object.
* @param object new target
* @param nestedPath the nested path of the object
* @param rootObject the root object at the top of the path
*/
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
Assert.notNull(object, "Bean object must not be null");
this.object = object;
this.nestedPath = (nestedPath != null ? nestedPath : "");
this.rootObject = (!"".equals(this.nestedPath) ? rootObject : object);
this.nestedBeanWrappers = null;
this.typeConverterDelegate = new TypeConverter(this, object);
setIntrospectionClass(object.getClass());
}
public Object getWrappedInstance() {
return this.object;
}
public Class getWrappedClass() {
return this.object.getClass();
}
/**
* Return the nested path of the object wrapped by this BeanWrapper.
*/
public String getNestedPath() {
return this.nestedPath;
}
/**
* Return the root object at the top of the path of this BeanWrapper.
* @see #getNestedPath
*/
public Object getRootInstance() {
return this.rootObject;
}
/**
* Return the class of the root object at the top of the path of this BeanWrapper.
* @see #getNestedPath
*/
public Class getRootClass() {
return (this.rootObject != null ? this.rootObject.getClass() : null);
}
/**
* 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) {
if (this.cachedIntrospectionResults == null ||
!this.cachedIntrospectionResults.getBeanClass().equals(clazz)) {
this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(clazz);
}
}
public PropertyDescriptor[] getPropertyDescriptors() {
return this.cachedIntrospectionResults.getBeanInfo().getPropertyDescriptors();
}
public PropertyDescriptor getPropertyDescriptor(String propertyName) throws BeansException {
Assert.notNull(propertyName, "Property name must not be null");
PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
if (pd != null) {
return pd;
}
else {
throw new PropertyException(getRootClass(), this.nestedPath + propertyName,
"No property '" + propertyName + "' found");
}
}
/**
* Internal version of getPropertyDescriptor:
* Returns <code>null</code> if not found rather than throwing an exception.
*/
protected PropertyDescriptor getPropertyDescriptorInternal(String propertyName) throws BeansException {
Assert.state(this.object != null, "BeanWrapper does not hold a bean instance");
BeanWrapper nestedBw = getBeanWrapperForPropertyPath(propertyName);
return nestedBw.cachedIntrospectionResults.getPropertyDescriptor(getFinalPath(nestedBw, propertyName));
}
public boolean isReadableProperty(String propertyName) {
Assert.notNull(propertyName, "Property name must not be null");
try {
PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
if (pd != null) {
if (pd.getReadMethod() != null) {
return true;
}
}
else {
// Maybe an indexed/mapped property...
getPropertyValue(propertyName);
return true;
}
}
catch (PropertyException ex) {
// Cannot be evaluated, so can't be readable.
}
return false;
}
public boolean isWritableProperty(String propertyName) {
Assert.notNull(propertyName, "Property name must not be null");
try {
PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
if (pd != null) {
if (pd.getWriteMethod() != null) {
return true;
}
}
else {
// Maybe an indexed/mapped property...
getPropertyValue(propertyName);
return true;
}
}
catch (PropertyException ex) {
// Cannot be evaluated, so can't be writable.
}
return false;
}
public Class getPropertyType(String propertyName) throws BeansException {
try {
PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
if (pd != null) {
return pd.getPropertyType();
}
else {
// Maybe an indexed/mapped property...
Object value = getPropertyValue(propertyName);
if (value != null) {
return value.getClass();
}
// Check to see if there is a custom editor,
// which might give an indication on the desired target type.
Class editorType = guessPropertyTypeFromEditors(propertyName);
if (editorType != null) {
return editorType;
}
}
}
catch (PropertyException ex) {
// Consider as not determinable.
}
return null;
}
//---------------------------------------------------------------------
// Implementation of TypeConverter interface
//---------------------------------------------------------------------
public Object convertIfNecessary(Object value, Class requiredType) throws TypeMismatchException {
return convertIfNecessary(value, requiredType, null);
}
public Object convertIfNecessary(
Object value, Class requiredType, MethodParameter methodParam) throws TypeMismatchException {
try {
return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam);
}
catch (IllegalArgumentException ex) {
throw new TypeMismatchException(value, requiredType, ex);
}
}
//---------------------------------------------------------------------
// Implementation methods
//---------------------------------------------------------------------
/**
* Get the last component of the path. Also works if not nested.
* @param bw BeanWrapper to work on
* @param nestedPath property path we know is nested
* @return last component of the path (the property on the target bean)
*/
private String getFinalPath(BeanWrapper bw, String nestedPath) {
if (bw == this) {
return nestedPath;
}
return nestedPath.substring(PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(nestedPath) + 1);
}
/**
* Recursively navigate to return a BeanWrapper for the nested property path.
* @param propertyPath property property path, which may be nested
* @return a BeanWrapper for the target bean
*/
protected BeanWrapper getBeanWrapperForPropertyPath(String propertyPath) throws BeansException {
int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
// handle nested properties recursively
if (pos > -1) {
String nestedProperty = propertyPath.substring(0, pos);
String nestedPath = propertyPath.substring(pos + 1);
BeanWrapper nestedBw = getNestedBeanWrapper(nestedProperty);
return nestedBw.getBeanWrapperForPropertyPath(nestedPath);
}
else {
return this;
}
}
/**
* Retrieve a BeanWrapper for the given nested property.
* Create a new one if not found in the cache.
* <p>Note: Caching nested BeanWrappers is necessary now,
* to keep registered custom editors for nested properties.
* @param nestedProperty property to create the BeanWrapper for
* @return the BeanWrapper instance, either cached or newly created
*/
private BeanWrapper getNestedBeanWrapper(String nestedProperty) throws BeansException {
if (this.nestedBeanWrappers == null) {
this.nestedBeanWrappers = new HashMap();
}
// Get Value of bean property-
PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
String canonicalName = tokens.canonicalName;
Object propertyValue = getPropertyValue(tokens);
if (propertyValue == null) {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName);
}
// Lookup cached sub-BeanWrapper, create new one if not found.
BeanWrapper nestedBw = (BeanWrapper) this.nestedBeanWrappers.get(canonicalName);
if (nestedBw == null || nestedBw.getWrappedInstance() != propertyValue) {
if (logger.isDebugEnabled()) {
logger.debug("Creating new nested BeanWrapper for property '" + canonicalName + "'");
}
nestedBw = newNestedBeanWrapper(propertyValue, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR);
// Inherit all type-specific PropertyEditors.
copyDefaultEditorsTo(nestedBw);
copyCustomEditorsTo(nestedBw, canonicalName);
this.nestedBeanWrappers.put(canonicalName, nestedBw);
}
else {
if (logger.isDebugEnabled()) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -