e.g. Calendar Search Help
You must enter a value before pressing Search
spring

Class: org.springframework.beans.BeanWrapperImpl   ©

 OK to copy?
0001 /*
0002  * Copyright 2002-2004 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.beans.PropertyEditor;
0022 import java.beans.PropertyEditorManager;
0023 import java.io.File;
0024 import java.io.InputStream;
0025 import java.lang.reflect.Array;
0026 import java.lang.reflect.InvocationTargetException;
0027 import java.lang.reflect.Method;
0028 import java.math.BigDecimal;
0029 import java.math.BigInteger;
0030 import java.net.URL;
0031 import java.util.ArrayList;
0032 import java.util.Collection;
0033 import java.util.HashMap;
0034 import java.util.Iterator;
0035 import java.util.LinkedList;
0036 import java.util.List;
0037 import java.util.Locale;
0038 import java.util.Map;
0039 import java.util.Properties;
0040 import java.util.Set;
0041 import java.util.SortedSet;
0042 
0043 import org.apache.commons.logging.Log;
0044 import org.apache.commons.logging.LogFactory;
0045 
0046 import org.springframework.beans.propertyeditors.ByteArrayPropertyEditor;
0047 import org.springframework.beans.propertyeditors.ClassEditor;
0048 import org.springframework.beans.propertyeditors.CustomBooleanEditor;
0049 import org.springframework.beans.propertyeditors.CustomCollectionEditor;
0050 import org.springframework.beans.propertyeditors.CustomNumberEditor;
0051 import org.springframework.beans.propertyeditors.FileEditor;
0052 import org.springframework.beans.propertyeditors.InputStreamEditor;
0053 import org.springframework.beans.propertyeditors.LocaleEditor;
0054 import org.springframework.beans.propertyeditors.PropertiesEditor;
0055 import org.springframework.beans.propertyeditors.StringArrayPropertyEditor;
0056 import org.springframework.beans.propertyeditors.URLEditor;
0057 import org.springframework.core.io.Resource;
0058 import org.springframework.core.io.support.ResourceArrayPropertyEditor;
0059 import org.springframework.util.Assert;
0060 import org.springframework.util.StringUtils;
0061 
0062 /**
0063  * Default implementation of the BeanWrapper interface that should be sufficient
0064  * for all typical use cases. Caches introspection results for efficiency.
0065  *
0066  * <p>Note: This class never tries to load a class by name, as this can pose
0067  * class loading problems in J2EE applications with multiple deployment modules.
0068  * The caller is responsible for loading a target class.
0069  *
0070  * <p>Note: Auto-registers default property editors from the
0071  * <code>org.springframework.beans.propertyeditors</code> package, which apply
0072  * in addition to the JDK's standard PropertyEditors. Applications can call
0073  * BeanWrapper's <code>registerCustomEditor</code> method to register an editor
0074  * for the particular instance (i.e. they're not shared across the application).
0075  *
0076  * <p>BeanWrapperImpl will convert collection and array values to the
0077  * corresponding target collections or arrays, if necessary. Custom property
0078  * editors that deal with collections or arrays can either be written via
0079  * PropertyEditor's <code>setValue</code>, or against a comma-delimited String
0080  * via <code>setAsText</code>, as String arrays are converted in such a format
0081  * if the array itself is not assignable.
0082  *
0083  * @author Rod Johnson
0084  * @author Juergen Hoeller
0085  * @since 15 April 2001
0086  * @see #registerCustomEditor
0087  * @see java.beans.PropertyEditorManager
0088  * @see java.beans.PropertyEditorSupport#setAsText
0089  * @see java.beans.PropertyEditorSupport#setValue
0090  * @see org.springframework.beans.propertyeditors.ByteArrayPropertyEditor
0091  * @see org.springframework.beans.propertyeditors.ClassEditor
0092  * @see org.springframework.beans.propertyeditors.CustomBooleanEditor
0093  * @see org.springframework.beans.propertyeditors.CustomNumberEditor
0094  * @see org.springframework.beans.propertyeditors.CustomCollectionEditor
0095  * @see org.springframework.beans.propertyeditors.FileEditor
0096  * @see org.springframework.beans.propertyeditors.InputStreamEditor
0097  * @see org.springframework.jndi.JndiTemplateEditor
0098  * @see org.springframework.beans.propertyeditors.LocaleEditor
0099  * @see org.springframework.beans.propertyeditors.PropertiesEditor
0100  * @see org.springframework.beans.PropertyValuesEditor
0101  * @see org.springframework.core.io.support.ResourceArrayPropertyEditor
0102  * @see org.springframework.core.io.ResourceEditor
0103  * @see org.springframework.beans.propertyeditors.StringArrayPropertyEditor
0104  * @see org.springframework.transaction.interceptor.TransactionAttributeEditor
0105  * @see org.springframework.transaction.interceptor.TransactionAttributeSourceEditor
0106  * @see org.springframework.beans.propertyeditors.URLEditor
0107  */
0108 public class BeanWrapperImpl implements BeanWrapper {
0109 
0110     /** We'll create a lot of these objects, so we don't want a new logger every time */
0111     private static final Log logger = LogFactory.getLog(BeanWrapperImpl.class);
0112 
0113 
0114     //---------------------------------------------------------------------
0115     // Instance data
0116     //---------------------------------------------------------------------
0117 
0118     /** The wrapped object */
0119     private Object object;
0120 
0121     /** The nested path of the object */
0122     private String nestedPath = "";
0123 
0124     private Object rootObject;
0125 
0126     /** Registry for default PropertyEditors */
0127     private final Map defaultEditors;
0128 
0129     /** Map with custom PropertyEditor instances */
0130     private Map customEditors;
0131 
0132     /**
0133      * Cached introspections results for this object, to prevent encountering
0134      * the cost of JavaBeans introspection every time.
0135      */
0136     private CachedIntrospectionResults cachedIntrospectionResults;
0137 
0138     /* Map with cached nested BeanWrappers */
0139     private Map nestedBeanWrappers;
0140 
0141 
0142     //---------------------------------------------------------------------
0143     // Constructors
0144     //---------------------------------------------------------------------
0145 
0146     /**
0147      * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
0148      * @see #setWrappedInstance
0149      */
0150     public BeanWrapperImpl() {
0151         // Register default editors in this class, for restricted environments.
0152         // We're not using the JRE's PropertyEditorManager to avoid potential
0153         // SecurityExceptions when running in a SecurityManager.
0154         this.defaultEditors = new HashMap(20);
0155 
0156         // Simple editors, without parameterization capabilities.
0157         this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
0158         this.defaultEditors.put(Class.class, new ClassEditor());
0159         this.defaultEditors.put(File.class, new FileEditor());
0160         this.defaultEditors.put(InputStream.class, new InputStreamEditor());
0161         this.defaultEditors.put(Locale.class, new LocaleEditor());
0162         this.defaultEditors.put(Properties.class, new PropertiesEditor());
0163         this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
0164         this.defaultEditors.put(String[].class, new StringArrayPropertyEditor());
0165         this.defaultEditors.put(URL.class, new URLEditor());
0166 
0167         // Default instances of boolean and number editors.
0168         // Can be overridden by registering custom instances of those as custom editors.
0169         this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(false));
0170         this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, false));
0171         this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, false));
0172         this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, false));
0173         this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, false));
0174         this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, false));
0175         this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, false));
0176         this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, false));
0177 
0178         // Default instances of collection editors.
0179         // Can be overridden by registering custom instances of those as custom editors.
0180         this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
0181         this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
0182         this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
0183         this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
0184     }
0185 
0186     /**
0187      * Create new BeanWrapperImpl for the given object.
0188      * @param object object wrapped by this BeanWrapper
0189      */
0190     public BeanWrapperImpl(Object object) {
0191         this();
0192         setWrappedInstance(object);
0193     }
0194 
0195     /**
0196      * Create new BeanWrapperImpl, wrapping a new instance of the specified class.
0197      * @param clazz class to instantiate and wrap
0198      */
0199     public BeanWrapperImpl(Class clazz) {
0200         this();
0201         setWrappedInstance(BeanUtils.instantiateClass(clazz));
0202     }
0203 
0204     /**
0205      * @deprecated in favor of BeanWrapperImpl(object, nestedPath, rootObject)
0206      * @see #BeanWrapperImpl(Object, String, Object)
0207      */
0208     public BeanWrapperImpl(Object object, String nestedPath) {
0209         this();
0210         setWrappedInstance(object, nestedPath);
0211     }
0212 
0213     /**
0214      * Create new BeanWrapperImpl for the given object,
0215      * registering a nested path that the object is in.
0216      * @param object object wrapped by this BeanWrapper.
0217      * @param nestedPath the nested path of the object
0218      * @param rootObject the root object at the top of the path
0219      */
0220     public BeanWrapperImpl(Object object, String nestedPath, Object rootObject) {
0221         this();
0222         setWrappedInstance(object, nestedPath, rootObject);
0223     }
0224 
0225     /**
0226      * Create new BeanWrapperImpl for the given object,
0227      * registering a nested path that the object is in.
0228      * @param object object wrapped by this BeanWrapper.
0229      * @param nestedPath the nested path of the object
0230      * @param superBw the containing BeanWrapper (must not be null)
0231      */
0232     private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl superBw) {
0233         this.defaultEditors = superBw.defaultEditors;
0234         setWrappedInstance(object, nestedPath, superBw.getWrappedInstance());
0235     }
0236 
0237 
0238     //---------------------------------------------------------------------
0239     // Implementation of BeanWrapper
0240     //---------------------------------------------------------------------
0241 
0242     /**
0243      * Switch the target object, replacing the cached introspection results only
0244      * if the class of the new object is different to that of the replaced object.
0245      * @param object new target
0246      */
0247     public void setWrappedInstance(Object object) {
0248         setWrappedInstance(object, "", null);
0249     }
0250 
0251     /**
0252      * @deprecated in favor of setWrappedInstance(object, nestedPath, rootObject)
0253      * @see #setWrappedInstance(Object, String, Object)
0254      */
0255     public void setWrappedInstance(Object object, String nestedPath) {
0256         setWrappedInstance(object, nestedPath, null);
0257     }
0258 
0259     /**
0260      * Switch the target object, replacing the cached introspection results only
0261      * if the class of the new object is different to that of the replaced object.
0262      * @param object new target
0263      * @param nestedPath the nested path of the object
0264      * @param rootObject the root object at the top of the path
0265      */
0266     public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
0267         if (object == null) {
0268             throw new IllegalArgumentException("Cannot set BeanWrapperImpl target to a null object");
0269         }
0270         this.object = object;
0271         this.nestedPath = (nestedPath != null ? nestedPath : "");
0272         this.rootObject = (!"".equals(this.nestedPath) ? rootObject : object);
0273         this.nestedBeanWrappers = null;
0274         setIntrospectionClass(object.getClass());
0275     }
0276 
0277     public Object getWrappedInstance() {
0278         return this.object;
0279     }
0280 
0281     public Class getWrappedClass() {
0282         return this.object.getClass();
0283     }
0284 
0285     /**
0286      * Return the nested path of the object wrapped by this BeanWrapper.
0287      */
0288     public String getNestedPath() {
0289         return this.nestedPath;
0290     }
0291 
0292     /**
0293      * Return the root object at the top of the path of this BeanWrapper.
0294      * @see #getNestedPath
0295      */
0296     public Object getRootInstance() {
0297         return this.rootObject;
0298     }
0299 
0300     /**
0301      * Return the class of the root object at the top of the path of this BeanWrapper.
0302      * @see #getNestedPath
0303      */
0304     public Class getRootClass() {
0305         return (this.rootObject != null ? this.rootObject.getClass() : null);
0306     }
0307 
0308     /**
0309      * Set the class to introspect.
0310      * Needs to be called when the target object changes.
0311      * @param clazz the class to introspect
0312      */
0313     protected void setIntrospectionClass(Class clazz) {
0314         if (this.cachedIntrospectionResults == null ||
0315             !this.cachedIntrospectionResults.getBeanClass().equals(clazz)) {
0316             this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(clazz);
0317         }
0318     }
0319 
0320 
0321     public void registerCustomEditor(Class requiredType, PropertyEditor propertyEditor) {
0322         registerCustomEditor(requiredType, null, propertyEditor);
0323     }
0324 
0325     public void registerCustomEditor(Class requiredType, String propertyPath, PropertyEditor propertyEditor) {
0326         if (requiredType == null && propertyPath == null) {
0327             throw new IllegalArgumentException("Either requiredType or propertyPath is required");
0328         }
0329         if (this.customEditors == null) {
0330             this.customEditors = new HashMap();
0331         }
0332         if (propertyPath != null) {
0333             this.customEditors.put(propertyPath, new CustomEditorHolder(propertyEditor, requiredType));
0334         }
0335         else {
0336             this.customEditors.put(requiredType, propertyEditor);
0337         }
0338     }
0339 
0340     public PropertyEditor findCustomEditor(Class requiredType, String propertyPath) {
0341         if (this.customEditors == null) {
0342             return null;
0343         }
0344         if (propertyPath != null) {
0345             // check property-specific editor first
0346             PropertyEditor editor = getCustomEditor(propertyPath, requiredType);
0347             if (editor == null) {
0348                 List strippedPaths = new LinkedList();
0349                 addStrippedPropertyPaths(strippedPaths, "", propertyPath);
0350                 for (Iterator it = strippedPaths.iterator(); it.hasNext() && editor == null;) {
0351                     String strippedPath = (String) it.next();
0352                     editor = getCustomEditor(strippedPath, requiredType);
0353                 }
0354             }
0355             if (editor != null) {
0356                 return editor;
0357             }
0358             else if (requiredType == null) {
0359                 requiredType = getPropertyType(propertyPath);
0360             }
0361         }
0362         // no property-specific editor -> check type-specific editor
0363         return getCustomEditor(requiredType);
0364     }
0365 
0366     /**
0367      * Get custom editor that has been registered for the given property.
0368      * @return the custom editor, or null if none specific for this property
0369      */
0370     private PropertyEditor getCustomEditor(String propertyName, Class requiredType) {
0371         CustomEditorHolder holder = (CustomEditorHolder) this.customEditors.get(propertyName);
0372         return (holder != null ? holder.getPropertyEditor(requiredType) : null);
0373     }
0374 
0375     /**
0376      * Get custom editor for the given type. If no direct match found,
0377      * try custom editor for superclass (which will in any case be able
0378      * to render a value as String via <code>getAsText</code>).
0379      * @see java.beans.PropertyEditor#getAsText
0380      * @return the custom editor, or null if none found for this type
0381      */
0382     private PropertyEditor getCustomEditor(Class requiredType) {
0383         if (requiredType != null) {
0384             PropertyEditor editor = (PropertyEditor) this.customEditors.get(requiredType);
0385             if (editor == null) {
0386                 for (Iterator it = this.customEditors.keySet().iterator(); it.hasNext();) {
0387                     Object key = it.next();
0388                     if (key instanceof Class && ((Class) key).isAssignableFrom(requiredType)) {
0389                         editor = (PropertyEditor) this.customEditors.get(key);
0390                     }
0391                 }
0392             }
0393             return editor;
0394         }
0395         return null;
0396     }
0397 
0398 
0399     /**
0400      * Add property paths with all variations of stripped keys and/or indexes.
0401      * Invokes itself recursively with nested paths
0402      * @param strippedPaths the result list to add to
0403      * @param nestedPath the current nested path
0404      * @param propertyPath the property path to check for keys/indexes to strip
0405      */
0406     private void addStrippedPropertyPaths(List strippedPaths, String nestedPath, String propertyPath) {
0407         int startIndex = propertyPath.indexOf(PROPERTY_KEY_PREFIX_CHAR);
0408         if (startIndex != -1) {
0409             int endIndex = propertyPath.indexOf(PROPERTY_KEY_SUFFIX_CHAR);
0410             if (endIndex != -1) {
0411                 String prefix = propertyPath.substring(0, startIndex);
0412                 String key = propertyPath.substring(startIndex, endIndex + 1);
0413                 String suffix = propertyPath.substring(endIndex + 1, propertyPath.length());
0414                 // strip the first key
0415                 strippedPaths.add(nestedPath + prefix + suffix);
0416                 // search for further keys to strip, with the first key stripped
0417                 addStrippedPropertyPaths(strippedPaths, nestedPath + prefix, suffix);
0418                 // search for further keys to strip, with the first key not stripped
0419                 addStrippedPropertyPaths(strippedPaths, nestedPath + prefix + key, suffix);
0420             }
0421         }
0422     }
0423 
0424     /**
0425      * Determine the first (or last) nested property separator in the
0426      * given property path, ignoring dots in keys (like "map[my.key]").
0427      * @param propertyPath the property path to check
0428      * @param last whether to return the last separator rather than the first
0429      * @return the index of the nested property separator, or -1 if none
0430      */
0431     private int getNestedPropertySeparatorIndex(String propertyPath, boolean last) {
0432         boolean inKey = false;
0433         int i = (last ? propertyPath.length()-1 : 0);
0434         while ((last && i >= 0) || i < propertyPath.length()) {
0435             switch (propertyPath.charAt(i)) {
0436                 case PROPERTY_KEY_PREFIX_CHAR:
0437                 case PROPERTY_KEY_SUFFIX_CHAR:
0438                     inKey = !inKey;
0439                     break;
0440                 case NESTED_PROPERTY_SEPARATOR_CHAR:
0441                     if (!inKey) {
0442                         return i;
0443                     }
0444             }
0445             if (last) i--; else i++;
0446         }
0447         return -1;
0448     }
0449 
0450     /**
0451      * Get the last component of the path. Also works if not nested.
0452      * @param bw BeanWrapper to work on
0453      * @param nestedPath property path we know is nested
0454      * @return last component of the path (the property on the target bean)
0455      */
0456     private String getFinalPath(BeanWrapper bw, String nestedPath) {
0457         if (bw == this) {
0458             return nestedPath;
0459         }
0460         return nestedPath.substring(getNestedPropertySeparatorIndex(nestedPath, true) + 1);
0461     }
0462 
0463     /**
0464      * Recursively navigate to return a BeanWrapper for the nested property path.
0465      * @param propertyPath property property path, which may be nested
0466      * @return a BeanWrapper for the target bean
0467      */
0468     protected BeanWrapperImpl getBeanWrapperForPropertyPath(String propertyPath) throws BeansException {
0469         int pos = getNestedPropertySeparatorIndex(propertyPath, false);
0470         // handle nested properties recursively
0471         if (pos > -1) {
0472             String nestedProperty = propertyPath.substring(0, pos);
0473             String nestedPath = propertyPath.substring(pos + 1);
0474             BeanWrapperImpl nestedBw = getNestedBeanWrapper(nestedProperty);
0475             return nestedBw.getBeanWrapperForPropertyPath(nestedPath);
0476         }
0477         else {
0478             return this;
0479         }
0480     }
0481 
0482     /**
0483      * Retrieve a BeanWrapper for the given nested property.
0484      * Create a new one if not found in the cache.
0485      * <p>Note: Caching nested BeanWrappers is necessary now,
0486      * to keep registered custom editors for nested properties.
0487      * @param nestedProperty property to create the BeanWrapper for
0488      * @return the BeanWrapper instance, either cached or newly created
0489      */
0490     private BeanWrapperImpl getNestedBeanWrapper(String nestedProperty) throws BeansException {
0491         if (this.nestedBeanWrappers == null) {
0492             this.nestedBeanWrappers = new HashMap();
0493         }
0494         // get value of bean property
0495         PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
0496         Object propertyValue = getPropertyValue(tokens);
0497         String canonicalName = tokens.canonicalName;
0498         String propertyName = tokens.actualName;
0499         if (propertyValue == null) {
0500             throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName);
0501         }
0502 
0503         // lookup cached sub-BeanWrapper, create new one if not found
0504         BeanWrapperImpl nestedBw = (BeanWrapperImpl) this.nestedBeanWrappers.get(canonicalName);
0505         if (nestedBw == null || nestedBw.getWrappedInstance() != propertyValue) {
0506             if (logger.isDebugEnabled()) {
0507                 logger.debug("Creating new nested BeanWrapper for property '" + canonicalName + "'");
0508             }
0509             nestedBw = new BeanWrapperImpl(
0510                     propertyValue, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR, this);
0511             // inherit all type-specific PropertyEditors
0512             if (this.customEditors != null) {
0513                 for (Iterator it = this.customEditors.entrySet().iterator(); it.hasNext();) {
0514                     Map.Entry entry = (Map.Entry) it.next();
0515                     if (entry.getKey() instanceof Class) {
0516                         Class requiredType = (Class) entry.getKey();
0517                         PropertyEditor editor = (PropertyEditor) entry.getValue();
0518                         nestedBw.registerCustomEditor(requiredType, editor);
0519                     }
0520                     else if (entry.getKey() instanceof String) {
0521                         String editorPath = (String) entry.getKey();
0522                         int pos = getNestedPropertySeparatorIndex(editorPath, false);
0523                         if (pos != -1) {
0524                             String editorNestedProperty = editorPath.substring(0, pos);
0525                             String editorNestedPath = editorPath.substring(pos + 1);
0526                             if (editorNestedProperty.equals(canonicalName) || editorNestedProperty.equals(propertyName)) {
0527                                 CustomEditorHolder editorHolder = (CustomEditorHolder) entry.getValue();
0528                                 nestedBw.registerCustomEditor(
0529                                         editorHolder.getRegisteredType(), editorNestedPath, editorHolder.getPropertyEditor());
0530                             }
0531                         }
0532                     }
0533                 }
0534             }
0535             this.nestedBeanWrappers.put(canonicalName, nestedBw);
0536         }
0537         else {
0538             if (logger.isDebugEnabled()) {
0539                 logger.debug("Using cached nested BeanWrapper for property '" + canonicalName + "'");
0540             }
0541         }
0542         return nestedBw;
0543     }
0544 
0545     private PropertyTokenHolder getPropertyNameTokens(String propertyName) {
0546         PropertyTokenHolder tokens = new PropertyTokenHolder();
0547         String actualName = null;
0548         List keys = new ArrayList(2);
0549         int searchIndex = 0;
0550         while (searchIndex != -1) {
0551             int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex);
0552             searchIndex = -1;
0553             if (keyStart != -1) {
0554                 int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length());
0555                 if (keyEnd != -1) {
0556                     if (actualName == null) {
0557                         actualName = propertyName.substring(0, keyStart);
0558                     }
0559                     String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd);
0560                     if (key.startsWith("'") && key.endsWith("'")) {
0561                         key = key.substring(1, key.length() - 1);
0562                     }
0563                     else if (key.startsWith("\"") && key.endsWith("\"")) {
0564                         key = key.substring(1, key.length() - 1);
0565                     }
0566                     keys.add(key);
0567                     searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length();
0568                 }
0569             }
0570         }
0571         tokens.actualName = (actualName != null ? actualName : propertyName);
0572         tokens.canonicalName = tokens.actualName;
0573         if (!keys.isEmpty()) {
0574             tokens.canonicalName +=
0575                     PROPERTY_KEY_PREFIX +
0576                     StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) +
0577                     PROPERTY_KEY_SUFFIX;
0578             tokens.keys = (String[]) keys.toArray(new String[keys.size()]);
0579         }
0580         return tokens;
0581     }
0582 
0583 
0584     public Object getPropertyValue(String propertyName) throws BeansException {
0585         BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName);
0586         PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
0587         return nestedBw.getPropertyValue(tokens);
0588     }
0589 
0590     protected Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
0591         String propertyName = tokens.canonicalName;
0592         String actualName = tokens.actualName;
0593         PropertyDescriptor pd = getPropertyDescriptorInternal(tokens.actualName);
0594         if (pd == null || pd.getReadMethod() == null) {
0595             throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);
0596         }
0597         if (logger.isDebugEnabled())
0598             logger.debug("About to invoke read method [" + pd.getReadMethod() + "] on object of class [" +
0599                     this.object.getClass().getName() + "]");
0600         try {
0601             Object value = pd.getReadMethod().invoke(this.object, (Object[]) null);
0602             if (tokens.keys != null) {
0603                 // apply indexes and map keys
0604                 for (int i = 0; i < tokens.keys.length; i++) {
0605                     String key = tokens.keys[i];
0606                     if (value == null) {
0607                         throw new NullValueInNestedPathException(
0608                                 getRootClass(), this.nestedPath + propertyName,
0609                                 "Cannot access indexed value of property referenced in indexed " +
0610                                 "property path '" + propertyName + "': returned null");
0611                     }
0612                     else if (value.getClass().isArray()) {
0613                         value = Array.get(value, Integer.parseInt(key));
0614                     }
0615                     else if (value instanceof List) {
0616                         List list = (List) value;
0617                         value = list.get(Integer.parseInt(key));
0618                     }
0619                     else if (value instanceof Set) {
0620                         // apply index to Iterator in case of a Set
0621                         Set set = (Set) value;
0622                         int index = Integer.parseInt(key);
0623                         if (index < 0 || index >= set.size()) {
0624                             throw new InvalidPropertyException(
0625                                     getRootClass(), this.nestedPath + propertyName,
0626                                     "Cannot get element with index " + index + " from Set of size " +
0627                                     set.size() + ", accessed using property path '" + propertyName + "'");
0628                         }
0629                         Iterator it = set.iterator();
0630                         for (int j = 0; it.hasNext(); j++) {
0631                             Object elem = it.next();
0632                             if (j == index) {
0633                                 value = elem;
0634                                 break;
0635                             }
0636                         }
0637                     }
0638                     else if (value instanceof Map) {
0639                         Map map = (Map) value;
0640                         value = map.get(key);
0641                     }
0642                     else {
0643                         throw new InvalidPropertyException(
0644                                 getRootClass(), this.nestedPath + propertyName,
0645                                 "Property referenced in indexed property path '" + propertyName +
0646                                 "' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");
0647                     }
0648                 }
0649             }
0650             return value;
0651         }
0652         catch (InvocationTargetException ex) {
0653             throw new InvalidPropertyException(
0654                     getRootClass(), this.nestedPath + propertyName,
0655                     "Getter for property '" + actualName + "' threw exception", ex);
0656         }
0657         catch (IllegalAccessException ex) {
0658             throw new InvalidPropertyException(
0659                     getRootClass(), this.nestedPath + propertyName,
0660                     "Illegal attempt to get property '" + actualName + "' threw exception", ex);
0661         }
0662         catch (IndexOutOfBoundsException ex) {
0663             throw new InvalidPropertyException(
0664                     getRootClass(), this.nestedPath + propertyName,
0665                     "Index of out of bounds in property path '" + propertyName + "'", ex);
0666         }
0667         catch (NumberFormatException ex) {
0668             throw new InvalidPropertyException(
0669                     getRootClass(), this.nestedPath + propertyName,
0670                     "Invalid index in property path '" + propertyName + "'", ex);
0671         }
0672     }
0673 
0674     public void setPropertyValue(String propertyName, Object value) throws BeansException {
0675         BeanWrapperImpl nestedBw = null;
0676         try {
0677             nestedBw = getBeanWrapperForPropertyPath(propertyName);
0678         }
0679         catch (NotReadablePropertyException ex) {
0680             throw new NotWritablePropertyException(
0681                     getRootClass(), this.nestedPath + propertyName,
0682                     "Nested property in path '" + propertyName + "' does not exist", ex);
0683         }
0684         PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
0685         nestedBw.setPropertyValue(tokens, value);
0686     }
0687 
0688     protected void setPropertyValue(PropertyTokenHolder tokens, Object value)
0689             throws BeansException {
0690         String propertyName = tokens.canonicalName;
0691 
0692         if (tokens.keys != null) {
0693             // apply indexes and map keys: fetch value for all keys but the last one
0694             PropertyTokenHolder getterTokens = new PropertyTokenHolder();
0695             getterTokens.canonicalName = tokens.canonicalName;
0696             getterTokens.actualName = tokens.actualName;
0697             getterTokens.keys = new String[tokens.keys.length - 1];
0698             System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1);
0699             Object propValue = null;
0700             try {
0701                 propValue = getPropertyValue(getterTokens);
0702             }
0703             catch (NotReadablePropertyException ex) {
0704                 throw new NotWritablePropertyException(
0705                         getRootClass(), this.nestedPath + propertyName,
0706                         "Cannot access indexed value in property referenced " +
0707                         "in indexed property path '" + propertyName + "'", ex);
0708             }
0709             // set value for last key
0710             String key = tokens.keys[tokens.keys.length - 1];
0711             if (propValue == null) {
0712                 throw new NullValueInNestedPathException(
0713                         getRootClass(), this.nestedPath + propertyName,
0714                         "Cannot access indexed value in property referenced " +
0715                         "in indexed property path '" + propertyName + "': returned null");
0716             }
0717             else if (propValue.getClass().isArray()) {
0718                 Class requiredType = propValue.getClass().getComponentType();
0719                 Object newValue = doTypeConversionIfNecessary(propertyName, propertyName, null, value, requiredType);
0720                 try {
Rate0721                     Array.set(propValue, Integer.parseInt(key), newValue);
0722                 }
0723                 catch (IllegalArgumentException ex) {
0724                     PropertyChangeEvent pce = new PropertyChangeEvent(
0725                             this.rootObject, this.nestedPath + propertyName, null, newValue);
0726                     throw new TypeMismatchException(pce, requiredType, ex);
0727                 }
0728                 catch (IndexOutOfBoundsException ex) {
0729                     throw new InvalidPropertyException(
0730                             getRootClass(), this.nestedPath + propertyName,
0731                             "Invalid array index in property path '" + propertyName + "'", ex);
0732                 }
0733             }
0734             else if (propValue instanceof List) {
0735                 Object newValue = doTypeConversionIfNecessary(propertyName, propertyName, null, value, null);
0736                 List list = (List) propValue;
0737                 int index = Integer.parseInt(key);
0738                 if (index < list.size()) {
0739                     list.set(index, newValue);
0740                 }
0741                 else if (index >= list.size()) {
0742                     for (int i = list.size(); i < index; i++) {
0743                         try {
0744                             list.add(null);
0745                         }
0746                         catch (NullPointerException ex) {
0747                             throw new InvalidPropertyException(
0748                                     getRootClass(), this.nestedPath + propertyName,
0749                                     "Cannot set element with index " + index + " in List of size " +
0750                                     list.size() + ", accessed using property path '" + propertyName +
0751                                     "': List does not support filling up gaps with null elements");
0752                         }
0753                     }
0754                     list.add(newValue);
0755                 }
0756             }
0757             else if (propValue instanceof Map) {
0758                 Object newValue = doTypeConversionIfNecessary(propertyName, propertyName, null, value, null);
0759                 Map map = (Map) propValue;
0760                 map.put(key, newValue);
0761             }
0762             else {
0763                 throw new InvalidPropertyException(
0764                         getRootClass(), this.nestedPath + propertyName,
0765                         "Property referenced in indexed property path '" + propertyName +
0766                         "' is neither an array nor a List nor a Map; returned value was [" + value + "]");
0767             }
0768         }
0769 
0770         else {
0771             if (!isWritableProperty(propertyName)) {
0772                 throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName);
0773             }
0774             PropertyDescriptor pd = getPropertyDescriptor(propertyName);
0775             Method writeMethod = pd.getWriteMethod();
0776             Object newValue = null;
0777             try {
0778                 // old value may still be null
0779                 newValue = doTypeConversionIfNecessary(propertyName, propertyName, null, value, pd.getPropertyType());
0780 
0781                 if (pd.getPropertyType().isPrimitive() && (newValue == null || "".equals(newValue))) {
0782                     throw new IllegalArgumentException("Invalid value [" + value + "] for property '" +
0783                                 pd.getName() + "' of primitive type [" + pd.getPropertyType() + "]");
0784                 }
0785 
0786                 if (logger.isDebugEnabled()) {
0787                     logger.debug("About to invoke write method [" + writeMethod + "] on object of class [" +
0788                             this.object.getClass().getName() + "]");
0789                 }
0790                 writeMethod.invoke(this.object, new Object[] { newValue });
0791                 if (logger.isDebugEnabled()) {
0792                     String msg = "Invoked write method [" + writeMethod + "] with value ";
0793                     // only cause toString invocation of new value in case of simple property
0794                     if (newValue == null || BeanUtils.isSimpleProperty(pd.getPropertyType())) {
0795                         logger.debug(msg + PROPERTY_KEY_PREFIX + newValue + PROPERTY_KEY_SUFFIX);
0796                     }
0797                     else {
0798                         logger.debug(msg + "of type [" + pd.getPropertyType().getName() + "]");
0799                     }
0800                 }
0801             }
0802             catch (InvocationTargetException ex) {
0803                 PropertyChangeEvent propertyChangeEvent =
0804                         new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, null, value);
0805                 if (ex.getTargetException() instanceof ClassCastException) {
0806                     throw new TypeMismatchException(propertyChangeEvent, pd.getPropertyType(), ex.getTargetException());
0807                 }
0808                 else {
0809                     throw new MethodInvocationException(propertyChangeEvent, ex.getTargetException());
0810                 }
0811             }
0812             catch (IllegalArgumentException ex) {
0813                 PropertyChangeEvent pce =
0814                         new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, null, value);
0815                 throw new TypeMismatchException(pce, pd.getPropertyType(), ex);
0816             }
0817             catch (IllegalAccessException ex) {
0818                 PropertyChangeEvent pce =
0819                         new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, null, value);
0820                 throw new MethodInvocationException(pce, ex);
0821             }
0822         }
0823     }
0824 
0825     public void setPropertyValue(PropertyValue pv) throws BeansException {
0826         setPropertyValue(pv.getName(), pv.getValue());
0827     }
0828 
0829     /**
0830      * Bulk update from a Map.
0831      * Bulk updates from PropertyValues are more powerful: this method is
0832      * provided for convenience.
0833      * @param map map containing properties to set, as name-value pairs.
0834      * The map may include nested properties.
0835      * @throws BeansException if there's a fatal, low-level exception
0836      */
0837     public void setPropertyValues(Map map) throws BeansException {
0838         setPropertyValues(new MutablePropertyValues(map));
0839     }
0840 
0841     public void setPropertyValues(PropertyValues pvs) throws BeansException {
0842         setPropertyValues(pvs, false);
0843     }
0844 
0845     public void setPropertyValues(PropertyValues propertyValues, boolean ignoreUnknown) throws BeansException {
0846         List propertyAccessExceptions = new ArrayList();
0847         PropertyValue[] pvs = propertyValues.getPropertyValues();
0848         for (int i = 0; i < pvs.length; i++) {
0849             try {
0850                 // This method may throw any BeansException, which won't be caught
0851                 // here, if there is a critical failure such as no matching field.
0852                 // We can attempt to deal only with less serious exceptions.
0853                 setPropertyValue(pvs[i]);
0854             }
0855             catch (NotWritablePropertyException ex) {
0856                 if (!ignoreUnknown) {
0857                     throw ex;
0858                 }
0859                 // otherwise, just ignore it and continue...
0860             }
0861             catch (PropertyAccessException ex) {
0862                 propertyAccessExceptions.add(ex);
0863             }
0864         }
0865 
0866         // If we encountered individual exceptions, throw the composite exception.
0867         if (!propertyAccessExceptions.isEmpty()) {
0868             Object[] paeArray = propertyAccessExceptions.toArray(
0869                     new PropertyAccessException[propertyAccessExceptions.size()]);
0870             throw new PropertyAccessExceptionsException(this, (PropertyAccessException[]) paeArray);
0871         }
0872     }
0873 
0874     private PropertyChangeEvent createPropertyChangeEvent(String propertyName, Object oldValue, Object newValue) {
0875         return new PropertyChangeEvent(
0876                 (this.rootObject != null ? this.rootObject : "constructor"),
0877                 (propertyName != null ? this.nestedPath + propertyName : null),
0878                 oldValue, newValue);
0879     }
0880 
0881     /**
0882      * Convert the value to the required type (if necessary from a String).
0883      * Conversions from String to any type use the setAsText method of
0884      * the PropertyEditor class. Note that a PropertyEditor must be registered
0885      * for this class for this to work. This is a standard Java Beans API.
0886      * A number of property editors are automatically registered by this class.
0887      * @param newValue proposed change value.
0888      * @param requiredType type we must convert to
0889      * @throws BeansException if there is an internal error
0890      * @return new value, possibly the result of type convertion
0891      */
0892     public Object doTypeConversionIfNecessary(Object newValue, Class requiredType) throws BeansException {
0893         return doTypeConversionIfNecessary(null, null, null, newValue, requiredType);
0894     }
0895 
0896     /**
0897      * Convert the value to the required type (if necessary from a String),
0898      * for the specified property.
0899      * @param propertyName name of the property
0900      * @param oldValue previous value, if available (may be null)
0901      * @param newValue proposed change value
0902      * @param requiredType the type we must convert to
0903      * (or null if not known, for example in case of a collection element)
0904      * @throws BeansException if there is an internal error
0905      * @return converted value (i.e. possibly the result of type conversion)
0906      */
0907     protected Object doTypeConversionIfNecessary(String propertyName, String fullPropertyName,
0908             Object oldValue, Object newValue, Class requiredType) throws BeansException {
0909 
0910         Object convertedValue = newValue;
0911         if (convertedValue != null) {
0912 
0913             // Custom editor for this type?
0914             PropertyEditor pe = findCustomEditor(requiredType, fullPropertyName);
0915 
0916             // Value not of required type?
0917             if (pe != null ||
0918                     (requiredType != null &&
0919                      (requiredType.isArray() || !requiredType.isAssignableFrom(convertedValue.getClass())))) {
0920 
0921                 if (requiredType != null) {
0922                     if (pe == null) {
0923                         // No custom editor -> check BeanWrapperImpl's default editors.
0924                         pe = (PropertyEditor) this.defaultEditors.get(requiredType);
0925                         if (pe == null) {
0926                             // No BeanWrapper default editor -> check standard JavaBean editors.
0927                             pe = PropertyEditorManager.findEditor(requiredType);
0928                         }
0929                     }
0930                 }
0931 
0932                 if (pe != null && !(convertedValue instanceof String)) {
0933                     // Not a String -> use PropertyEditor's setValue.
0934                     // With standard PropertyEditors, this will return the very same object;
0935                     // we just want to allow special PropertyEditors to override setValue
0936                     // for type conversion from non-String values to the required type.
0937                     try {
0938                         pe.setValue(convertedValue);
0939                         convertedValue = pe.getValue();
0940                     }
0941                     catch (IllegalArgumentException ex) {
0942                         throw new TypeMismatchException(
0943                                 createPropertyChangeEvent(fullPropertyName, oldValue, newValue), requiredType, ex);
0944                     }
0945                 }
0946 
0947                 if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) {
0948                     // Convert String array to a comma-separated String.
0949                     // Only applies if no PropertyEditor converted the String array before.
0950                     // The CSV String will be passed into a PropertyEditor's setAsText method, if any.
0951                     if (logger.isDebugEnabled()) {
0952                         logger.debug("Converting String array to comma-delimited String [" + convertedValue + "]");
0953                     }
0954                     convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue);
0955                 }
0956 
0957                 if (pe != null && convertedValue instanceof String) {
0958                     // Use PropertyEditor's setAsText in case of a String value.
0959                     if (logger.isDebugEnabled()) {
0960                         logger.debug("Converting String to [" + requiredType + "] using property editor [" + pe + "]");
0961                     }
0962                     try {
0963                         pe.setAsText((String) convertedValue);
0964                         convertedValue = pe.getValue();
0965                     }
0966                     catch (IllegalArgumentException ex) {
0967                         throw new TypeMismatchException(
0968                                 createPropertyChangeEvent(fullPropertyName, oldValue, newValue), requiredType, ex);
0969                     }
0970                 }
0971 
0972                 if (requiredType != null) {
0973                     // Array required -> apply appropriate conversion of elements.
0974                     if (requiredType.isArray()) {
0975                         Class componentType = requiredType.getComponentType();
0976                         if (convertedValue instanceof Collection) {
0977                             // Convert Collection elements to array elements.
0978                             Collection coll = (Collection) convertedValue;
0979                             Object result = Array.newInstance(componentType, coll.size());
0980                             int i = 0;
0981                             for (Iterator it = coll.iterator(); it.hasNext(); i++) {
0982                                 Object value = doTypeConversionIfNecessary(
0983                                         propertyName, propertyName + PROPERTY_KEY_PREFIX + i + PROPERTY_KEY_SUFFIX,
0984                                         null, it.next(), componentType);
Rate0985                                 Array.set(result, i, value);
0986                             }
0987                             return result;
0988                         }
0989                         else if (convertedValue != null && convertedValue.getClass().isArray()) {
0990                             // Convert Collection elements to array elements.
0991                             int arrayLength = Array.getLength(convertedValue);
0992                             Object result = Array.newInstance(componentType, arrayLength);
0993                             for (int i = 0; i < arrayLength; i++) {
0994                                 Object value = doTypeConversionIfNecessary(
0995                                         propertyName, propertyName + PROPERTY_KEY_PREFIX + i + PROPERTY_KEY_SUFFIX,
0996                                         null, Array.get(convertedValue, i), componentType);
Rate0997                                 Array.set(result, i, value);
0998                             }
0999                             return result;
1000                         }
1001                         else {
1002                             // A plain value: convert it to an array with a single component.
1003                             Object result = Array.newInstance(componentType, 1) ;
1004                             Object val = doTypeConversionIfNecessary(
1005                                     propertyName, propertyName + PROPERTY_KEY_PREFIX + 0 + PROPERTY_KEY_SUFFIX,
1006                                     null, convertedValue, componentType);
Rate1007                             Array.set(result, 0, val);
1008                             return result;
1009                         }
1010                     }
1011 
1012                     // Throw explicit TypeMismatchException with full context information
1013                     // if the resulting value definitely doesn't match the required type.
1014                     if (convertedValue != null && !requiredType.isPrimitive() &&
1015                             !requiredType.isAssignableFrom(convertedValue.getClass())) {
1016                         throw new TypeMismatchException(
1017                                 createPropertyChangeEvent(fullPropertyName, oldValue, newValue), requiredType);
1018                     }
1019                 }
1020             }
1021         }
1022 
1023         return convertedValue;
1024     }
1025 
1026 
1027     public PropertyDescriptor[] getPropertyDescriptors() {
1028         return this.cachedIntrospectionResults.getBeanInfo().getPropertyDescriptors();
1029     }
1030 
1031     public PropertyDescriptor getPropertyDescriptor(String propertyName) throws BeansException {
1032         if (propertyName == null) {
1033             throw new IllegalArgumentException("Can't find property descriptor for null property");
1034         }
1035         PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
1036         if (pd != null) {
1037             return pd;
1038         }
1039         else {
1040             throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
1041                 "No property '" + propertyName + "' found");
1042         }
1043     }
1044 
1045     /**
1046      * Internal version of getPropertyDescriptor:
1047      * Returns null if not found rather than throwing an exception.
1048      */
1049     protected PropertyDescriptor getPropertyDescriptorInternal(String propertyName) throws BeansException {
1050         Assert.state(this.object != null, "BeanWrapper does not hold a bean instance");
1051         BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName);
1052         return nestedBw.cachedIntrospectionResults.getPropertyDescriptor(getFinalPath(nestedBw, propertyName));
1053     }
1054 
1055     public Class getPropertyType(String propertyName) throws BeansException {
1056         try {
1057             PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
1058             if (pd != null) {
1059                 return pd.getPropertyType();
1060             }
1061             else {
1062                 // maybe an indexed/mapped property
1063                 Object value = getPropertyValue(propertyName);
1064                 if (value != null) {
1065                     return value.getClass();
1066                 }
1067             }
1068         }
1069         catch (InvalidPropertyException ex) {
1070             // consider as not determinable
1071         }
1072         return null;
1073     }
1074 
1075     public boolean isReadableProperty(String propertyName) {
1076         // This is a programming error, although asking for a property
1077         // that doesn't exist is not.
1078         if (propertyName == null) {
1079             throw new IllegalArgumentException("Can't find readability status for null property");
1080         }
1081         try {
1082             PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
1083             if (pd != null) {
1084                 if (pd.getReadMethod() != null) {
1085                     return true;
1086                 }
1087             }
1088             else {
1089                 // maybe an indexed/mapped property
1090                 getPropertyValue(propertyName);
1091                 return true;
1092             }
1093         }
1094         catch (InvalidPropertyException ex) {
1095             // cannot be evaluated, so can't be readable
1096         }
1097         return false;
1098     }
1099 
1100     public boolean isWritableProperty(String propertyName) {
1101         // This is a programming error, although asking for a property
1102         // that doesn't exist is not.
1103         if (propertyName == null) {
1104             throw new IllegalArgumentException("Can't find writability status for null property");
1105         }
1106         try {
1107             PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
1108             if (pd != null) {
1109                 if (pd.getWriteMethod() != null) {
1110                     return true;
1111                 }
1112             }
1113             else {
1114                 // maybe an indexed/mapped property
1115                 getPropertyValue(propertyName);
1116                 return true;
1117             }
1118         }
1119         catch (InvalidPropertyException ex) {
1120             // cannot be evaluated, so can't be writable
1121         }
1122         return false;
1123     }
1124 
1125 
1126     //---------------------------------------------------------------------
1127     // Diagnostics
1128     //---------------------------------------------------------------------
1129 
1130     public String toString() {
1131         StringBuffer sb = new StringBuffer("BeanWrapperImpl: wrapping class [");
1132         sb.append(getWrappedClass().getName()).append("]");
1133         return sb.toString();
1134     }
1135 
1136 
1137     /**
1138      * Holder for a registered custom editor with property name.
1139      * Keeps the PropertyEditor itself plus the type it was registered for.
1140      */
1141     private static class CustomEditorHolder {
1142 
1143         private final PropertyEditor propertyEditor;
1144 
1145         private final Class registeredType;
1146 
1147         private CustomEditorHolder(PropertyEditor propertyEditor, Class registeredType) {
1148             this.propertyEditor = propertyEditor;
1149             this.registeredType = registeredType;
1150         }
1151 
1152         private PropertyEditor getPropertyEditor() {
1153             return propertyEditor;
1154         }
1155 
1156         private Class getRegisteredType() {
1157             return registeredType;
1158         }
1159 
1160         private PropertyEditor getPropertyEditor(Class requiredType) {
1161             // Special case: If no required type specified, which usually only happens for
1162             // Collection elements, or required type is not assignable to registered type,
1163             // which usually only happens for generic properties of type Object -
1164             // then return PropertyEditor if not registered for Collection or array type.
1165             // (If not registered for Collection or array, it is assumed to be intended
1166             // for elements.)
1167             if (this.registeredType == null ||
1168                     (requiredType != null &&
1169                 (BeanUtils.isAssignable(this.registeredType, requiredType) ||
1170                 BeanUtils.isAssignable(requiredType, this.registeredType))) ||
1171                     (requiredType == null &&
1172                 (!Collection.class.isAssignableFrom(this.registeredType) && !this.registeredType.isArray()))) {
1173                 return this.propertyEditor;
1174             }
1175             else {
1176                 return null;
1177             }
1178         }
1179     }
1180 
1181 
1182     private static class PropertyTokenHolder {
1183 
1184         private String canonicalName;
1185 
1186         private String actualName;
1187 
1188         private String[] keys;
1189     }
1190 
1191 }

            
All Examples in File:
Example
Line
Rating (found
useful by...)
721 0% of 0
985 0% of 0
997 0% of 0
1007 0% of 0