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 }