0001: /*
0002: * Copyright 2002-2007 the original author or authors.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: package org.springframework.beans;
0018:
0019: import java.beans.PropertyChangeEvent;
0020: import java.beans.PropertyDescriptor;
0021: import java.lang.reflect.Array;
0022: import java.lang.reflect.InvocationTargetException;
0023: import java.lang.reflect.Method;
0024: import java.lang.reflect.Modifier;
0025: import java.util.ArrayList;
0026: import java.util.HashMap;
0027: import java.util.Iterator;
0028: import java.util.List;
0029: import java.util.Map;
0030: import java.util.Set;
0031:
0032: import org.apache.commons.logging.Log;
0033: import org.apache.commons.logging.LogFactory;
0034:
0035: import org.springframework.core.GenericCollectionTypeResolver;
0036: import org.springframework.core.JdkVersion;
0037: import org.springframework.core.MethodParameter;
0038: import org.springframework.util.Assert;
0039: import org.springframework.util.ObjectUtils;
0040: import org.springframework.util.StringUtils;
0041:
0042: /**
0043: * Default {@link BeanWrapper} implementation that should be sufficient
0044: * for all typical use cases. Caches introspection results for efficiency.
0045: *
0046: * <p>Note: Auto-registers default property editors from the
0047: * <code>org.springframework.beans.propertyeditors</code> package, which apply
0048: * in addition to the JDK's standard PropertyEditors. Applications can call
0049: * the {@link #registerCustomEditor(Class, java.beans.PropertyEditor)} method
0050: * to register an editor for a particular instance (i.e. they are not shared
0051: * across the application). See the base class
0052: * {@link PropertyEditorRegistrySupport} for details.
0053: *
0054: * <p><code>BeanWrapperImpl</code> will convert collection and array values
0055: * to the corresponding target collections or arrays, if necessary. Custom
0056: * property editors that deal with collections or arrays can either be
0057: * written via PropertyEditor's <code>setValue</code>, or against a
0058: * comma-delimited String via <code>setAsText</code>, as String arrays are
0059: * converted in such a format if the array itself is not assignable.
0060: *
0061: * @author Rod Johnson
0062: * @author Juergen Hoeller
0063: * @author Rob Harrop
0064: * @since 15 April 2001
0065: * @see #registerCustomEditor
0066: * @see #setPropertyValues
0067: * @see #setPropertyValue
0068: * @see #getPropertyValue
0069: * @see #getPropertyType
0070: * @see BeanWrapper
0071: * @see PropertyEditorRegistrySupport
0072: */
0073: public class BeanWrapperImpl extends AbstractPropertyAccessor implements
0074: BeanWrapper {
0075:
0076: /**
0077: * We'll create a lot of these objects, so we don't want a new logger every time.
0078: */
0079: private static final Log logger = LogFactory
0080: .getLog(BeanWrapperImpl.class);
0081:
0082: /** The wrapped object */
0083: private Object object;
0084:
0085: private String nestedPath = "";
0086:
0087: private Object rootObject;
0088:
0089: private TypeConverterDelegate typeConverterDelegate;
0090:
0091: /**
0092: * Cached introspections results for this object, to prevent encountering
0093: * the cost of JavaBeans introspection every time.
0094: */
0095: private CachedIntrospectionResults cachedIntrospectionResults;
0096:
0097: /**
0098: * Map with cached nested BeanWrappers: nested path -> BeanWrapper instance.
0099: */
0100: private Map nestedBeanWrappers;
0101:
0102: /**
0103: * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
0104: * Registers default editors.
0105: * @see #setWrappedInstance
0106: */
0107: public BeanWrapperImpl() {
0108: this (true);
0109: }
0110:
0111: /**
0112: * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
0113: * @param registerDefaultEditors whether to register default editors
0114: * (can be suppressed if the BeanWrapper won't need any type conversion)
0115: * @see #setWrappedInstance
0116: */
0117: public BeanWrapperImpl(boolean registerDefaultEditors) {
0118: if (registerDefaultEditors) {
0119: registerDefaultEditors();
0120: }
0121: this .typeConverterDelegate = new TypeConverterDelegate(this );
0122: }
0123:
0124: /**
0125: * Create new BeanWrapperImpl for the given object.
0126: * @param object object wrapped by this BeanWrapper
0127: */
0128: public BeanWrapperImpl(Object object) {
0129: registerDefaultEditors();
0130: setWrappedInstance(object);
0131: }
0132:
0133: /**
0134: * Create new BeanWrapperImpl, wrapping a new instance of the specified class.
0135: * @param clazz class to instantiate and wrap
0136: */
0137: public BeanWrapperImpl(Class clazz) {
0138: registerDefaultEditors();
0139: setWrappedInstance(BeanUtils.instantiateClass(clazz));
0140: }
0141:
0142: /**
0143: * Create new BeanWrapperImpl for the given object,
0144: * registering a nested path that the object is in.
0145: * @param object object wrapped by this BeanWrapper
0146: * @param nestedPath the nested path of the object
0147: * @param rootObject the root object at the top of the path
0148: */
0149: public BeanWrapperImpl(Object object, String nestedPath,
0150: Object rootObject) {
0151: registerDefaultEditors();
0152: setWrappedInstance(object, nestedPath, rootObject);
0153: }
0154:
0155: /**
0156: * Create new BeanWrapperImpl for the given object,
0157: * registering a nested path that the object is in.
0158: * @param object object wrapped by this BeanWrapper
0159: * @param nestedPath the nested path of the object
0160: * @param superBw the containing BeanWrapper (must not be <code>null</code>)
0161: */
0162: private BeanWrapperImpl(Object object, String nestedPath,
0163: BeanWrapperImpl super Bw) {
0164: setWrappedInstance(object, nestedPath, super Bw
0165: .getWrappedInstance());
0166: setExtractOldValueForEditor(super Bw
0167: .isExtractOldValueForEditor());
0168: }
0169:
0170: //---------------------------------------------------------------------
0171: // Implementation of BeanWrapper interface
0172: //---------------------------------------------------------------------
0173:
0174: /**
0175: * Switch the target object, replacing the cached introspection results only
0176: * if the class of the new object is different to that of the replaced object.
0177: * @param object the new target object
0178: */
0179: public void setWrappedInstance(Object object) {
0180: setWrappedInstance(object, "", null);
0181: }
0182:
0183: /**
0184: * Switch the target object, replacing the cached introspection results only
0185: * if the class of the new object is different to that of the replaced object.
0186: * @param object the new target object
0187: * @param nestedPath the nested path of the object
0188: * @param rootObject the root object at the top of the path
0189: */
0190: public void setWrappedInstance(Object object, String nestedPath,
0191: Object rootObject) {
0192: Assert.notNull(object, "Bean object must not be null");
0193: this .object = object;
0194: this .nestedPath = (nestedPath != null ? nestedPath : "");
0195: this .rootObject = (!"".equals(this .nestedPath) ? rootObject
0196: : object);
0197: this .nestedBeanWrappers = null;
0198: this .typeConverterDelegate = new TypeConverterDelegate(this ,
0199: object);
0200: setIntrospectionClass(object.getClass());
0201: }
0202:
0203: public final Object getWrappedInstance() {
0204: return this .object;
0205: }
0206:
0207: public final Class getWrappedClass() {
0208: return (this .object != null ? this .object.getClass() : null);
0209: }
0210:
0211: /**
0212: * Return the nested path of the object wrapped by this BeanWrapper.
0213: */
0214: public final String getNestedPath() {
0215: return this .nestedPath;
0216: }
0217:
0218: /**
0219: * Return the root object at the top of the path of this BeanWrapper.
0220: * @see #getNestedPath
0221: */
0222: public final Object getRootInstance() {
0223: return this .rootObject;
0224: }
0225:
0226: /**
0227: * Return the class of the root object at the top of the path of this BeanWrapper.
0228: * @see #getNestedPath
0229: */
0230: public final Class getRootClass() {
0231: return (this .rootObject != null ? this .rootObject.getClass()
0232: : null);
0233: }
0234:
0235: /**
0236: * Set the class to introspect.
0237: * Needs to be called when the target object changes.
0238: * @param clazz the class to introspect
0239: */
0240: protected void setIntrospectionClass(Class clazz) {
0241: if (this .cachedIntrospectionResults != null
0242: && !clazz.equals(this .cachedIntrospectionResults
0243: .getBeanClass())) {
0244: this .cachedIntrospectionResults = null;
0245: }
0246: }
0247:
0248: /**
0249: * Obtain a lazily initializted CachedIntrospectionResults instance
0250: * for the wrapped object.
0251: */
0252: private CachedIntrospectionResults getCachedIntrospectionResults() {
0253: Assert.state(this .object != null,
0254: "BeanWrapper does not hold a bean instance");
0255: if (this .cachedIntrospectionResults == null) {
0256: this .cachedIntrospectionResults = CachedIntrospectionResults
0257: .forClass(getWrappedClass());
0258: }
0259: return this .cachedIntrospectionResults;
0260: }
0261:
0262: public PropertyDescriptor[] getPropertyDescriptors() {
0263: return getCachedIntrospectionResults().getBeanInfo()
0264: .getPropertyDescriptors();
0265: }
0266:
0267: public PropertyDescriptor getPropertyDescriptor(String propertyName)
0268: throws BeansException {
0269: PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
0270: if (pd == null) {
0271: throw new InvalidPropertyException(getRootClass(),
0272: this .nestedPath + propertyName, "No property '"
0273: + propertyName + "' found");
0274: }
0275: return pd;
0276: }
0277:
0278: /**
0279: * Internal version of {@link #getPropertyDescriptor}:
0280: * Returns <code>null</code> if not found rather than throwing an exception.
0281: * @param propertyName the property to obtain the descriptor for
0282: * @return the property descriptor for the specified property,
0283: * or <code>null</code> if not found
0284: * @throws BeansException in case of introspection failure
0285: */
0286: protected PropertyDescriptor getPropertyDescriptorInternal(
0287: String propertyName) throws BeansException {
0288: Assert.notNull(propertyName, "Property name must not be null");
0289: BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName);
0290: return nestedBw.getCachedIntrospectionResults()
0291: .getPropertyDescriptor(
0292: getFinalPath(nestedBw, propertyName));
0293: }
0294:
0295: public Class getPropertyType(String propertyName)
0296: throws BeansException {
0297: try {
0298: PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
0299: if (pd != null) {
0300: return pd.getPropertyType();
0301: } else {
0302: // Maybe an indexed/mapped property...
0303: Object value = getPropertyValue(propertyName);
0304: if (value != null) {
0305: return value.getClass();
0306: }
0307: // Check to see if there is a custom editor,
0308: // which might give an indication on the desired target type.
0309: Class editorType = guessPropertyTypeFromEditors(propertyName);
0310: if (editorType != null) {
0311: return editorType;
0312: }
0313: }
0314: } catch (InvalidPropertyException ex) {
0315: // Consider as not determinable.
0316: }
0317: return null;
0318: }
0319:
0320: public boolean isReadableProperty(String propertyName) {
0321: try {
0322: PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
0323: if (pd != null) {
0324: if (pd.getReadMethod() != null) {
0325: return true;
0326: }
0327: } else {
0328: // Maybe an indexed/mapped property...
0329: getPropertyValue(propertyName);
0330: return true;
0331: }
0332: } catch (InvalidPropertyException ex) {
0333: // Cannot be evaluated, so can't be readable.
0334: }
0335: return false;
0336: }
0337:
0338: public boolean isWritableProperty(String propertyName) {
0339: try {
0340: PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
0341: if (pd != null) {
0342: if (pd.getWriteMethod() != null) {
0343: return true;
0344: }
0345: } else {
0346: // Maybe an indexed/mapped property...
0347: getPropertyValue(propertyName);
0348: return true;
0349: }
0350: } catch (InvalidPropertyException ex) {
0351: // Cannot be evaluated, so can't be writable.
0352: }
0353: return false;
0354: }
0355:
0356: //---------------------------------------------------------------------
0357: // Implementation of TypeConverter interface
0358: //---------------------------------------------------------------------
0359:
0360: /**
0361: * @deprecated in favor of <code>convertIfNecessary</code>
0362: * @see #convertIfNecessary(Object, Class)
0363: */
0364: public Object doTypeConversionIfNecessary(Object value,
0365: Class requiredType) throws TypeMismatchException {
0366: return convertIfNecessary(value, requiredType, null);
0367: }
0368:
0369: public Object convertIfNecessary(Object value, Class requiredType)
0370: throws TypeMismatchException {
0371: return convertIfNecessary(value, requiredType, null);
0372: }
0373:
0374: public Object convertIfNecessary(Object value, Class requiredType,
0375: MethodParameter methodParam) throws TypeMismatchException {
0376: try {
0377: return this .typeConverterDelegate.convertIfNecessary(value,
0378: requiredType, methodParam);
0379: } catch (IllegalArgumentException ex) {
0380: throw new TypeMismatchException(value, requiredType, ex);
0381: }
0382: }
0383:
0384: /**
0385: * Convert the given value for the specified property to the latter's type.
0386: * <p>This method is only intended for optimizations in a BeanFactory.
0387: * Use the <code>convertIfNecessary</code> methods for programmatic conversion.
0388: * @param value the value to convert
0389: * @param propertyName the target property
0390: * (note that nested or indexed properties are not supported here)
0391: * @return the new value, possibly the result of type conversion
0392: * @throws TypeMismatchException if type conversion failed
0393: */
0394: public Object convertForProperty(Object value, String propertyName)
0395: throws TypeMismatchException {
0396: PropertyDescriptor pd = getCachedIntrospectionResults()
0397: .getPropertyDescriptor(propertyName);
0398: if (pd == null) {
0399: throw new InvalidPropertyException(getRootClass(),
0400: this .nestedPath + propertyName, "No property '"
0401: + propertyName + "' found");
0402: }
0403: try {
0404: return this .typeConverterDelegate.convertIfNecessary(null,
0405: value, pd);
0406: } catch (IllegalArgumentException ex) {
0407: PropertyChangeEvent pce = new PropertyChangeEvent(
0408: this .rootObject, this .nestedPath + propertyName,
0409: null, value);
0410: throw new TypeMismatchException(pce, pd.getPropertyType(),
0411: ex);
0412: }
0413: }
0414:
0415: //---------------------------------------------------------------------
0416: // Implementation methods
0417: //---------------------------------------------------------------------
0418:
0419: /**
0420: * Get the last component of the path. Also works if not nested.
0421: * @param bw BeanWrapper to work on
0422: * @param nestedPath property path we know is nested
0423: * @return last component of the path (the property on the target bean)
0424: */
0425: private String getFinalPath(BeanWrapper bw, String nestedPath) {
0426: if (bw == this ) {
0427: return nestedPath;
0428: }
0429: return nestedPath.substring(PropertyAccessorUtils
0430: .getLastNestedPropertySeparatorIndex(nestedPath) + 1);
0431: }
0432:
0433: /**
0434: * Recursively navigate to return a BeanWrapper for the nested property path.
0435: * @param propertyPath property property path, which may be nested
0436: * @return a BeanWrapper for the target bean
0437: */
0438: protected BeanWrapperImpl getBeanWrapperForPropertyPath(
0439: String propertyPath) {
0440: int pos = PropertyAccessorUtils
0441: .getFirstNestedPropertySeparatorIndex(propertyPath);
0442: // Handle nested properties recursively.
0443: if (pos > -1) {
0444: String nestedProperty = propertyPath.substring(0, pos);
0445: String nestedPath = propertyPath.substring(pos + 1);
0446: BeanWrapperImpl nestedBw = getNestedBeanWrapper(nestedProperty);
0447: return nestedBw.getBeanWrapperForPropertyPath(nestedPath);
0448: } else {
0449: return this ;
0450: }
0451: }
0452:
0453: /**
0454: * Retrieve a BeanWrapper for the given nested property.
0455: * Create a new one if not found in the cache.
0456: * <p>Note: Caching nested BeanWrappers is necessary now,
0457: * to keep registered custom editors for nested properties.
0458: * @param nestedProperty property to create the BeanWrapper for
0459: * @return the BeanWrapper instance, either cached or newly created
0460: */
0461: private BeanWrapperImpl getNestedBeanWrapper(String nestedProperty) {
0462: if (this .nestedBeanWrappers == null) {
0463: this .nestedBeanWrappers = new HashMap();
0464: }
0465: // Get value of bean property.
0466: PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
0467: String canonicalName = tokens.canonicalName;
0468: Object propertyValue = getPropertyValue(tokens);
0469: if (propertyValue == null) {
0470: throw new NullValueInNestedPathException(getRootClass(),
0471: this .nestedPath + canonicalName);
0472: }
0473:
0474: // Lookup cached sub-BeanWrapper, create new one if not found.
0475: BeanWrapperImpl nestedBw = (BeanWrapperImpl) this .nestedBeanWrappers
0476: .get(canonicalName);
0477: if (nestedBw == null
0478: || nestedBw.getWrappedInstance() != propertyValue) {
0479: if (logger.isTraceEnabled()) {
0480: logger
0481: .trace("Creating new nested BeanWrapper for property '"
0482: + canonicalName + "'");
0483: }
0484: nestedBw = newNestedBeanWrapper(propertyValue,
0485: this .nestedPath + canonicalName
0486: + NESTED_PROPERTY_SEPARATOR);
0487: // Inherit all type-specific PropertyEditors.
0488: copyDefaultEditorsTo(nestedBw);
0489: copyCustomEditorsTo(nestedBw, canonicalName);
0490: this .nestedBeanWrappers.put(canonicalName, nestedBw);
0491: } else {
0492: if (logger.isTraceEnabled()) {
0493: logger
0494: .trace("Using cached nested BeanWrapper for property '"
0495: + canonicalName + "'");
0496: }
0497: }
0498: return nestedBw;
0499: }
0500:
0501: /**
0502: * Create a new nested BeanWrapper instance.
0503: * <p>Default implementation creates a BeanWrapperImpl instance.
0504: * Can be overridden in subclasses to create a BeanWrapperImpl subclass.
0505: * @param object object wrapped by this BeanWrapper
0506: * @param nestedPath the nested path of the object
0507: * @return the nested BeanWrapper instance
0508: */
0509: protected BeanWrapperImpl newNestedBeanWrapper(Object object,
0510: String nestedPath) {
0511: return new BeanWrapperImpl(object, nestedPath, this );
0512: }
0513:
0514: /**
0515: * Parse the given property name into the corresponding property name tokens.
0516: * @param propertyName the property name to parse
0517: * @return representation of the parsed property tokens
0518: */
0519: private PropertyTokenHolder getPropertyNameTokens(
0520: String propertyName) {
0521: PropertyTokenHolder tokens = new PropertyTokenHolder();
0522: String actualName = null;
0523: List keys = new ArrayList(2);
0524: int searchIndex = 0;
0525: while (searchIndex != -1) {
0526: int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX,
0527: searchIndex);
0528: searchIndex = -1;
0529: if (keyStart != -1) {
0530: int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX,
0531: keyStart + PROPERTY_KEY_PREFIX.length());
0532: if (keyEnd != -1) {
0533: if (actualName == null) {
0534: actualName = propertyName
0535: .substring(0, keyStart);
0536: }
0537: String key = propertyName.substring(keyStart
0538: + PROPERTY_KEY_PREFIX.length(), keyEnd);
0539: if ((key.startsWith("'") && key.endsWith("'"))
0540: || (key.startsWith("\"") && key
0541: .endsWith("\""))) {
0542: key = key.substring(1, key.length() - 1);
0543: }
0544: keys.add(key);
0545: searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length();
0546: }
0547: }
0548: }
0549: tokens.actualName = (actualName != null ? actualName
0550: : propertyName);
0551: tokens.canonicalName = tokens.actualName;
0552: if (!keys.isEmpty()) {
0553: tokens.canonicalName += PROPERTY_KEY_PREFIX
0554: + StringUtils.collectionToDelimitedString(keys,
0555: PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX)
0556: + PROPERTY_KEY_SUFFIX;
0557: tokens.keys = StringUtils.toStringArray(keys);
0558: }
0559: return tokens;
0560: }
0561:
0562: //---------------------------------------------------------------------
0563: // Implementation of PropertyAccessor interface
0564: //---------------------------------------------------------------------
0565:
0566: public Object getPropertyValue(String propertyName)
0567: throws BeansException {
0568: BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName);
0569: PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(
0570: nestedBw, propertyName));
0571: return nestedBw.getPropertyValue(tokens);
0572: }
0573:
0574: private Object getPropertyValue(PropertyTokenHolder tokens)
0575: throws BeansException {
0576: String propertyName = tokens.canonicalName;
0577: String actualName = tokens.actualName;
0578: PropertyDescriptor pd = getCachedIntrospectionResults()
0579: .getPropertyDescriptor(actualName);
0580: if (pd == null || pd.getReadMethod() == null) {
0581: throw new NotReadablePropertyException(getRootClass(),
0582: this .nestedPath + propertyName);
0583: }
0584: Method readMethod = pd.getReadMethod();
0585: try {
0586: if (!Modifier.isPublic(readMethod.getDeclaringClass()
0587: .getModifiers())) {
0588: readMethod.setAccessible(true);
0589: }
0590: Object value = readMethod.invoke(this .object,
0591: (Object[]) null);
0592: if (tokens.keys != null) {
0593: // apply indexes and map keys
0594: for (int i = 0; i < tokens.keys.length; i++) {
0595: String key = tokens.keys[i];
0596: if (value == null) {
0597: throw new NullValueInNestedPathException(
0598: getRootClass(), this .nestedPath
0599: + propertyName,
0600: "Cannot access indexed value of property referenced in indexed "
0601: + "property path '"
0602: + propertyName
0603: + "': returned null");
0604: } else if (value.getClass().isArray()) {
0605: value = Array.get(value, Integer.parseInt(key));
0606: } else if (value instanceof List) {
0607: List list = (List) value;
0608: value = list.get(Integer.parseInt(key));
0609: } else if (value instanceof Set) {
0610: // Apply index to Iterator in case of a Set.
0611: Set set = (Set) value;
0612: int index = Integer.parseInt(key);
0613: if (index < 0 || index >= set.size()) {
0614: throw new InvalidPropertyException(
0615: getRootClass(),
0616: this .nestedPath + propertyName,
0617: "Cannot get element with index "
0618: + index
0619: + " from Set of size "
0620: + set.size()
0621: + ", accessed using property path '"
0622: + propertyName + "'");
0623: }
0624: Iterator it = set.iterator();
0625: for (int j = 0; it.hasNext(); j++) {
0626: Object elem = it.next();
0627: if (j == index) {
0628: value = elem;
0629: break;
0630: }
0631: }
0632: } else if (value instanceof Map) {
0633: Map map = (Map) value;
0634: Class mapKeyType = null;
0635: if (JdkVersion.isAtLeastJava15()) {
0636: mapKeyType = GenericCollectionTypeResolver
0637: .getMapKeyReturnType(pd
0638: .getReadMethod(), i + 1);
0639: }
0640: // IMPORTANT: Do not pass full property name in here - property editors
0641: // must not kick in for map keys but rather only for map values.
0642: Object convertedMapKey = this .typeConverterDelegate
0643: .convertIfNecessary(key, mapKeyType);
0644: // Pass full property name and old value in here, since we want full
0645: // conversion ability for map values.
0646: value = map.get(convertedMapKey);
0647: } else {
0648: throw new InvalidPropertyException(
0649: getRootClass(),
0650: this .nestedPath + propertyName,
0651: "Property referenced in indexed property path '"
0652: + propertyName
0653: + "' is neither an array nor a List nor a Set nor a Map; returned value was ["
0654: + value + "]");
0655: }
0656: }
0657: }
0658: return value;
0659: } catch (InvocationTargetException ex) {
0660: throw new InvalidPropertyException(getRootClass(),
0661: this .nestedPath + propertyName,
0662: "Getter for property '" + actualName
0663: + "' threw exception", ex);
0664: } catch (IllegalAccessException ex) {
0665: throw new InvalidPropertyException(getRootClass(),
0666: this .nestedPath + propertyName,
0667: "Illegal attempt to get property '" + actualName
0668: + "' threw exception", ex);
0669: } catch (IndexOutOfBoundsException ex) {
0670: throw new InvalidPropertyException(getRootClass(),
0671: this .nestedPath + propertyName,
0672: "Index of out of bounds in property path '"
0673: + propertyName + "'", ex);
0674: } catch (NumberFormatException ex) {
0675: throw new InvalidPropertyException(getRootClass(),
0676: this .nestedPath + propertyName,
0677: "Invalid index in property path '" + propertyName
0678: + "'", ex);
0679: }
0680: }
0681:
0682: public void setPropertyValue(String propertyName, Object value)
0683: throws BeansException {
0684: BeanWrapperImpl nestedBw = null;
0685: try {
0686: nestedBw = getBeanWrapperForPropertyPath(propertyName);
0687: } catch (NotReadablePropertyException ex) {
0688: throw new NotWritablePropertyException(getRootClass(),
0689: this .nestedPath + propertyName,
0690: "Nested property in path '" + propertyName
0691: + "' does not exist", ex);
0692: }
0693: PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(
0694: nestedBw, propertyName));
0695: nestedBw.setPropertyValue(tokens, new PropertyValue(
0696: propertyName, value));
0697: }
0698:
0699: public void setPropertyValue(PropertyValue pv)
0700: throws BeansException {
0701: PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
0702: if (tokens == null) {
0703: String propertyName = pv.getName();
0704: BeanWrapperImpl nestedBw = null;
0705: try {
0706: nestedBw = getBeanWrapperForPropertyPath(propertyName);
0707: } catch (NotReadablePropertyException ex) {
0708: throw new NotWritablePropertyException(getRootClass(),
0709: this .nestedPath + propertyName,
0710: "Nested property in path '" + propertyName
0711: + "' does not exist", ex);
0712: }
0713: tokens = getPropertyNameTokens(getFinalPath(nestedBw,
0714: propertyName));
0715: if (nestedBw == this ) {
0716: pv.getOriginalPropertyValue().resolvedTokens = tokens;
0717: }
0718: nestedBw.setPropertyValue(tokens, pv);
0719: } else {
0720: setPropertyValue(tokens, pv);
0721: }
0722: }
0723:
0724: private void setPropertyValue(PropertyTokenHolder tokens,
0725: PropertyValue pv) throws BeansException {
0726: String propertyName = tokens.canonicalName;
0727: String actualName = tokens.actualName;
0728:
0729: if (tokens.keys != null) {
0730: // Apply indexes and map keys: fetch value for all keys but the last one.
0731: PropertyTokenHolder getterTokens = new PropertyTokenHolder();
0732: getterTokens.canonicalName = tokens.canonicalName;
0733: getterTokens.actualName = tokens.actualName;
0734: getterTokens.keys = new String[tokens.keys.length - 1];
0735: System.arraycopy(tokens.keys, 0, getterTokens.keys, 0,
0736: tokens.keys.length - 1);
0737: Object propValue = null;
0738: try {
0739: propValue = getPropertyValue(getterTokens);
0740: } catch (NotReadablePropertyException ex) {
0741: throw new NotWritablePropertyException(getRootClass(),
0742: this .nestedPath + propertyName,
0743: "Cannot access indexed value in property referenced "
0744: + "in indexed property path '"
0745: + propertyName + "'", ex);
0746: }
0747: // Set value for last key.
0748: String key = tokens.keys[tokens.keys.length - 1];
0749: if (propValue == null) {
0750: throw new NullValueInNestedPathException(
0751: getRootClass(), this .nestedPath + propertyName,
0752: "Cannot access indexed value in property referenced "
0753: + "in indexed property path '"
0754: + propertyName + "': returned null");
0755: } else if (propValue.getClass().isArray()) {
0756: Class requiredType = propValue.getClass()
0757: .getComponentType();
0758: int arrayIndex = Integer.parseInt(key);
0759: Object oldValue = null;
0760: try {
0761: if (isExtractOldValueForEditor()) {
0762: oldValue = Array.get(propValue, arrayIndex);
0763: }
0764: Object convertedValue = this .typeConverterDelegate
0765: .convertIfNecessary(propertyName, oldValue,
0766: pv.getValue(), requiredType);
0767: Array.set(propValue, Integer.parseInt(key),
0768: convertedValue);
0769: } catch (IllegalArgumentException ex) {
0770: PropertyChangeEvent pce = new PropertyChangeEvent(
0771: this .rootObject, this .nestedPath
0772: + propertyName, oldValue, pv
0773: .getValue());
0774: throw new TypeMismatchException(pce, requiredType,
0775: ex);
0776: } catch (IndexOutOfBoundsException ex) {
0777: throw new InvalidPropertyException(getRootClass(),
0778: this .nestedPath + propertyName,
0779: "Invalid array index in property path '"
0780: + propertyName + "'", ex);
0781: }
0782: } else if (propValue instanceof List) {
0783: PropertyDescriptor pd = getCachedIntrospectionResults()
0784: .getPropertyDescriptor(actualName);
0785: Class requiredType = null;
0786: if (JdkVersion.isAtLeastJava15()) {
0787: requiredType = GenericCollectionTypeResolver
0788: .getCollectionReturnType(
0789: pd.getReadMethod(),
0790: tokens.keys.length);
0791: }
0792: List list = (List) propValue;
0793: int index = Integer.parseInt(key);
0794: Object oldValue = null;
0795: if (isExtractOldValueForEditor() && index < list.size()) {
0796: oldValue = list.get(index);
0797: }
0798: try {
0799: Object convertedValue = this .typeConverterDelegate
0800: .convertIfNecessary(propertyName, oldValue,
0801: pv.getValue(), requiredType);
0802: if (index < list.size()) {
0803: list.set(index, convertedValue);
0804: } else if (index >= list.size()) {
0805: for (int i = list.size(); i < index; i++) {
0806: try {
0807: list.add(null);
0808: } catch (NullPointerException ex) {
0809: throw new InvalidPropertyException(
0810: getRootClass(),
0811: this .nestedPath + propertyName,
0812: "Cannot set element with index "
0813: + index
0814: + " in List of size "
0815: + list.size()
0816: + ", accessed using property path '"
0817: + propertyName
0818: + "': List does not support filling up gaps with null elements");
0819: }
0820: }
0821: list.add(convertedValue);
0822: }
0823: } catch (IllegalArgumentException ex) {
0824: PropertyChangeEvent pce = new PropertyChangeEvent(
0825: this .rootObject, this .nestedPath
0826: + propertyName, oldValue, pv
0827: .getValue());
0828: throw new TypeMismatchException(pce, requiredType,
0829: ex);
0830: }
0831: } else if (propValue instanceof Map) {
0832: PropertyDescriptor pd = getCachedIntrospectionResults()
0833: .getPropertyDescriptor(actualName);
0834: Class mapKeyType = null;
0835: Class mapValueType = null;
0836: if (JdkVersion.isAtLeastJava15()) {
0837: mapKeyType = GenericCollectionTypeResolver
0838: .getMapKeyReturnType(pd.getReadMethod(),
0839: tokens.keys.length);
0840: mapValueType = GenericCollectionTypeResolver
0841: .getMapValueReturnType(pd.getReadMethod(),
0842: tokens.keys.length);
0843: }
0844: Map map = (Map) propValue;
0845: Object convertedMapKey = null;
0846: Object convertedMapValue = null;
0847: try {
0848: // IMPORTANT: Do not pass full property name in here - property editors
0849: // must not kick in for map keys but rather only for map values.
0850: convertedMapKey = this .typeConverterDelegate
0851: .convertIfNecessary(key, mapKeyType);
0852: } catch (IllegalArgumentException ex) {
0853: PropertyChangeEvent pce = new PropertyChangeEvent(
0854: this .rootObject, this .nestedPath
0855: + propertyName, null, pv.getValue());
0856: throw new TypeMismatchException(pce, mapKeyType, ex);
0857: }
0858: Object oldValue = null;
0859: if (isExtractOldValueForEditor()) {
0860: oldValue = map.get(convertedMapKey);
0861: }
0862: try {
0863: // Pass full property name and old value in here, since we want full
0864: // conversion ability for map values.
0865: convertedMapValue = this .typeConverterDelegate
0866: .convertIfNecessary(propertyName, oldValue,
0867: pv.getValue(), mapValueType, null,
0868: new MethodParameter(pd
0869: .getReadMethod(), -1,
0870: tokens.keys.length + 1));
0871: } catch (IllegalArgumentException ex) {
0872: PropertyChangeEvent pce = new PropertyChangeEvent(
0873: this .rootObject, this .nestedPath
0874: + propertyName, oldValue, pv
0875: .getValue());
0876: throw new TypeMismatchException(pce, mapValueType,
0877: ex);
0878: }
0879: map.put(convertedMapKey, convertedMapValue);
0880: } else {
0881: throw new InvalidPropertyException(
0882: getRootClass(),
0883: this .nestedPath + propertyName,
0884: "Property referenced in indexed property path '"
0885: + propertyName
0886: + "' is neither an array nor a List nor a Map; returned value was ["
0887: + pv.getValue() + "]");
0888: }
0889: }
0890:
0891: else {
0892: PropertyDescriptor pd = pv.resolvedDescriptor;
0893: if (pd == null
0894: || !pd.getWriteMethod().getDeclaringClass()
0895: .isInstance(this .object)) {
0896: pd = getCachedIntrospectionResults()
0897: .getPropertyDescriptor(actualName);
0898: if (pd == null || pd.getWriteMethod() == null) {
0899: PropertyMatches matches = PropertyMatches
0900: .forProperty(propertyName, getRootClass());
0901: throw new NotWritablePropertyException(
0902: getRootClass(), this .nestedPath
0903: + propertyName, matches
0904: .buildErrorMessage(), matches
0905: .getPossibleMatches());
0906: }
0907: pv.getOriginalPropertyValue().resolvedDescriptor = pd;
0908: }
0909:
0910: Object oldValue = null;
0911: try {
0912: Object originalValue = pv.getValue();
0913: Object valueToApply = originalValue;
0914: if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
0915: if (pv.isConverted()) {
0916: valueToApply = pv.getConvertedValue();
0917: } else {
0918: if (isExtractOldValueForEditor()
0919: && pd.getReadMethod() != null) {
0920: Method readMethod = pd.getReadMethod();
0921: if (!Modifier
0922: .isPublic(readMethod
0923: .getDeclaringClass()
0924: .getModifiers())) {
0925: readMethod.setAccessible(true);
0926: }
0927: try {
0928: oldValue = readMethod.invoke(
0929: this .object, new Object[0]);
0930: } catch (Exception ex) {
0931: if (logger.isDebugEnabled()) {
0932: logger.debug(
0933: "Could not read previous value of property '"
0934: + this .nestedPath
0935: + propertyName
0936: + "'", ex);
0937: }
0938: }
0939: }
0940: valueToApply = this .typeConverterDelegate
0941: .convertIfNecessary(oldValue,
0942: originalValue, pd);
0943: }
0944: pv.getOriginalPropertyValue().conversionNecessary = Boolean
0945: .valueOf(valueToApply != originalValue);
0946: }
0947: Method writeMethod = pd.getWriteMethod();
0948: if (!Modifier.isPublic(writeMethod.getDeclaringClass()
0949: .getModifiers())) {
0950: writeMethod.setAccessible(true);
0951: }
0952: writeMethod.invoke(this .object,
0953: new Object[] { valueToApply });
0954: } catch (InvocationTargetException ex) {
0955: PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
0956: this .rootObject,
0957: this .nestedPath + propertyName, oldValue, pv
0958: .getValue());
0959: if (ex.getTargetException() instanceof ClassCastException) {
0960: throw new TypeMismatchException(
0961: propertyChangeEvent, pd.getPropertyType(),
0962: ex.getTargetException());
0963: } else {
0964: throw new MethodInvocationException(
0965: propertyChangeEvent, ex
0966: .getTargetException());
0967: }
0968: } catch (IllegalArgumentException ex) {
0969: PropertyChangeEvent pce = new PropertyChangeEvent(
0970: this .rootObject,
0971: this .nestedPath + propertyName, oldValue, pv
0972: .getValue());
0973: throw new TypeMismatchException(pce, pd
0974: .getPropertyType(), ex);
0975: } catch (IllegalAccessException ex) {
0976: PropertyChangeEvent pce = new PropertyChangeEvent(
0977: this .rootObject,
0978: this .nestedPath + propertyName, oldValue, pv
0979: .getValue());
0980: throw new MethodInvocationException(pce, ex);
0981: }
0982: }
0983: }
0984:
0985: public String toString() {
0986: StringBuffer sb = new StringBuffer(getClass().getName());
0987: if (this .object != null) {
0988: sb.append(": wrapping object [").append(
0989: ObjectUtils.identityToString(this .object)).append(
0990: "]");
0991: } else {
0992: sb.append(": no wrapped object set");
0993: }
0994: return sb.toString();
0995: }
0996:
0997: //---------------------------------------------------------------------
0998: // Inner class for internal use
0999: //---------------------------------------------------------------------
1000:
1001: private static class PropertyTokenHolder {
1002:
1003: public String canonicalName;
1004:
1005: public String actualName;
1006:
1007: public String[] keys;
1008: }
1009:
1010: }
|