BeanWrapperImpl.java
Upload User: jiancairen
Upload Date: 2007-08-27
Package Size: 26458k
Code Size: 41k
Category:

Java Develop

Development Platform:

Java

  1. /*
  2.  * Copyright 2002-2004 the original author or authors.
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *      http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16. package org.springframework.beans;
  17. import java.beans.PropertyChangeEvent;
  18. import java.beans.PropertyDescriptor;
  19. import java.beans.PropertyEditor;
  20. import java.beans.PropertyEditorManager;
  21. import java.io.File;
  22. import java.io.InputStream;
  23. import java.lang.reflect.Array;
  24. import java.lang.reflect.InvocationTargetException;
  25. import java.lang.reflect.Method;
  26. import java.math.BigDecimal;
  27. import java.math.BigInteger;
  28. import java.net.URL;
  29. import java.util.ArrayList;
  30. import java.util.Collection;
  31. import java.util.HashMap;
  32. import java.util.Iterator;
  33. import java.util.LinkedList;
  34. import java.util.List;
  35. import java.util.Locale;
  36. import java.util.Map;
  37. import java.util.Properties;
  38. import java.util.Set;
  39. import org.apache.commons.logging.Log;
  40. import org.apache.commons.logging.LogFactory;
  41. import org.springframework.beans.propertyeditors.ByteArrayPropertyEditor;
  42. import org.springframework.beans.propertyeditors.ClassEditor;
  43. import org.springframework.beans.propertyeditors.CustomBooleanEditor;
  44. import org.springframework.beans.propertyeditors.CustomNumberEditor;
  45. import org.springframework.beans.propertyeditors.FileEditor;
  46. import org.springframework.beans.propertyeditors.InputStreamEditor;
  47. import org.springframework.beans.propertyeditors.LocaleEditor;
  48. import org.springframework.beans.propertyeditors.PropertiesEditor;
  49. import org.springframework.beans.propertyeditors.StringArrayPropertyEditor;
  50. import org.springframework.beans.propertyeditors.URLEditor;
  51. import org.springframework.util.StringUtils;
  52. /**
  53.  * Default implementation of the BeanWrapper interface that should be sufficient
  54.  * for all normal uses. Caches introspection results for efficiency.
  55.  *
  56.  * <p>Note: This class never tries to load a class by name, as this can pose
  57.  * class loading problems in J2EE applications with multiple deployment modules.
  58.  * The caller is responsible for loading a target class.
  59.  *
  60.  * <p>Note: Auto-registers all default property editors (not the custom ones)
  61.  * in the org.springframework.beans.propertyeditors package.
  62.  * Applications can either use a standard PropertyEditorManager to register a
  63.  * custom editor before using a BeanWrapperImpl instance, or call the instance's
  64.  * registerCustomEditor method to register an editor for the particular instance.
  65.  *
  66.  * <p>BeanWrapperImpl will convert List and array values to the corresponding
  67.  * target arrays, if necessary. Custom property editors that deal with Lists or
  68.  * arrays can be written against a comma delimited String as String arrays are
  69.  * converted in such a format if the array itself is not assignable.
  70.  *
  71.  * @author Rod Johnson
  72.  * @author Juergen Hoeller
  73.  * @author Jean-Pierre Pawlak
  74.  * @since 15 April 2001
  75.  * @see #registerCustomEditor
  76.  * @see java.beans.PropertyEditorManager
  77.  * @see org.springframework.beans.propertyeditors.ClassEditor
  78.  * @see org.springframework.beans.propertyeditors.FileEditor
  79.  * @see org.springframework.beans.propertyeditors.LocaleEditor
  80.  * @see org.springframework.beans.propertyeditors.PropertiesEditor
  81.  * @see org.springframework.beans.propertyeditors.StringArrayPropertyEditor
  82.  * @see org.springframework.beans.propertyeditors.URLEditor
  83.  */
  84. public class BeanWrapperImpl implements BeanWrapper {
  85. /** We'll create a lot of these objects, so we don't want a new logger every time */
  86. private static final Log logger = LogFactory.getLog(BeanWrapperImpl.class);
  87. //---------------------------------------------------------------------
  88. // Instance data
  89. //---------------------------------------------------------------------
  90. /** The wrapped object */
  91. private Object object;
  92. /** The nested path of the object */
  93. private String nestedPath = "";
  94. /** Registry for default PropertyEditors */
  95. private final Map defaultEditors;
  96. /** Map with custom PropertyEditor instances */
  97. private Map customEditors;
  98. /**
  99.  * Cached introspections results for this object, to prevent encountering the cost
  100.  * of JavaBeans introspection every time.
  101.  */
  102. private CachedIntrospectionResults cachedIntrospectionResults;
  103. /* Map with cached nested BeanWrappers */
  104. private Map nestedBeanWrappers;
  105. //---------------------------------------------------------------------
  106. // Constructors
  107. //---------------------------------------------------------------------
  108. /**
  109.  * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
  110.  * @see #setWrappedInstance
  111.  */
  112. public BeanWrapperImpl() {
  113. // Register default editors in this class, for restricted environments.
  114. // We're not using the JRE's PropertyEditorManager to avoid potential
  115. // SecurityExceptions when running in a SecurityManager.
  116. this.defaultEditors = new HashMap(16);
  117. // Simple editors, without parameterization capabilities.
  118. this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
  119. this.defaultEditors.put(Class.class, new ClassEditor());
  120. this.defaultEditors.put(File.class, new FileEditor());
  121. this.defaultEditors.put(InputStream.class, new InputStreamEditor());
  122. this.defaultEditors.put(Locale.class, new LocaleEditor());
  123. this.defaultEditors.put(Properties.class, new PropertiesEditor());
  124. this.defaultEditors.put(String[].class, new StringArrayPropertyEditor());
  125. this.defaultEditors.put(URL.class, new URLEditor());
  126. // Default instances of parameterizable editors.
  127. // Can be overridden by registering custom instances of those as custom editors.
  128. this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(false));
  129. this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, false));
  130. this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, false));
  131. this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, false));
  132. this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, false));
  133. this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, false));
  134. this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, false));
  135. this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, false));
  136. }
  137. /**
  138.  * Create new BeanWrapperImpl for the given object.
  139.  * @param object object wrapped by this BeanWrapper
  140.  */
  141. public BeanWrapperImpl(Object object) {
  142. this();
  143. setWrappedInstance(object);
  144. }
  145. /**
  146.  * Create new BeanWrapperImpl, wrapping a new instance of the specified class.
  147.  * @param clazz class to instantiate and wrap
  148.  */
  149. public BeanWrapperImpl(Class clazz) {
  150. this();
  151. setWrappedInstance(BeanUtils.instantiateClass(clazz));
  152. }
  153. /**
  154.  * Create new BeanWrapperImpl for the given object,
  155.  * registering a nested path that the object is in.
  156.  * @param object object wrapped by this BeanWrapper.
  157.  * @param nestedPath the nested path of the object
  158.  */
  159. public BeanWrapperImpl(Object object, String nestedPath) {
  160. this();
  161. setWrappedInstance(object, nestedPath);
  162. }
  163. /**
  164.  * Create new BeanWrapperImpl for the given object,
  165.  * registering a nested path that the object is in.
  166.  * @param object object wrapped by this BeanWrapper.
  167.  * @param nestedPath the nested path of the object
  168.  * @param superBw the containing BeanWrapper (must not be null)
  169.  */
  170. private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl superBw) {
  171. this.defaultEditors = superBw.defaultEditors;
  172. setWrappedInstance(object, nestedPath);
  173. }
  174. //---------------------------------------------------------------------
  175. // Implementation of BeanWrapper
  176. //---------------------------------------------------------------------
  177. /**
  178.  * Switch the target object, replacing the cached introspection results only
  179.  * if the class of the new object is different to that of the replaced object.
  180.  * @param object new target
  181.  */
  182. public void setWrappedInstance(Object object) {
  183. setWrappedInstance(object, "");
  184. }
  185. /**
  186.  * Switch the target object, replacing the cached introspection results only
  187.  * if the class of the new object is different to that of the replaced object.
  188.  * @param object new target
  189.  * @param nestedPath the nested path of the object
  190.  */
  191. public void setWrappedInstance(Object object, String nestedPath) {
  192. if (object == null) {
  193. throw new IllegalArgumentException("Cannot set BeanWrapperImpl target to a null object");
  194. }
  195. this.object = object;
  196. this.nestedPath = nestedPath;
  197. this.nestedBeanWrappers = null;
  198. setIntrospectionClass(object.getClass());
  199. }
  200. public Object getWrappedInstance() {
  201. return this.object;
  202. }
  203. public Class getWrappedClass() {
  204. return this.object.getClass();
  205. }
  206. /**
  207.  * Set the class to introspect.
  208.  * Needs to be called when the target object changes.
  209.  * @param clazz the class to introspect
  210.  */
  211. protected void setIntrospectionClass(Class clazz) {
  212. if (this.cachedIntrospectionResults == null ||
  213.     !this.cachedIntrospectionResults.getBeanClass().equals(clazz)) {
  214. this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(clazz);
  215. }
  216. }
  217. public void registerCustomEditor(Class requiredType, PropertyEditor propertyEditor) {
  218. registerCustomEditor(requiredType, null, propertyEditor);
  219. }
  220. public void registerCustomEditor(Class requiredType, String propertyPath, PropertyEditor propertyEditor) {
  221. if (requiredType == null && propertyPath == null) {
  222. throw new IllegalArgumentException("Either requiredType or propertyPath is required");
  223. }
  224. if (this.customEditors == null) {
  225. this.customEditors = new HashMap();
  226. }
  227. if (propertyPath != null) {
  228. this.customEditors.put(propertyPath, new CustomEditorHolder(propertyEditor, requiredType));
  229. }
  230. else {
  231. this.customEditors.put(requiredType, propertyEditor);
  232. }
  233. }
  234. public PropertyEditor findCustomEditor(Class requiredType, String propertyPath) {
  235. if (this.customEditors == null) {
  236. return null;
  237. }
  238. if (propertyPath != null) {
  239. // check property-specific editor first
  240. PropertyEditor editor = getCustomEditor(propertyPath, requiredType);
  241. if (editor == null) {
  242. List strippedPaths = new LinkedList();
  243. addStrippedPropertyPaths(strippedPaths, "", propertyPath);
  244. for (Iterator it = strippedPaths.iterator(); it.hasNext() && editor == null;) {
  245. String strippedPath = (String) it.next();
  246. editor = getCustomEditor(strippedPath, requiredType);
  247. }
  248. }
  249. if (editor != null) {
  250. return editor;
  251. }
  252. else if (requiredType == null) {
  253. requiredType = getPropertyType(propertyPath);
  254. }
  255. }
  256. // no property-specific editor -> check type-specific editor
  257. return getCustomEditor(requiredType);
  258. }
  259. /**
  260.  * Get custom editor that has been registered for the given property.
  261.  * @return the custom editor, or null if none specific for this property
  262.  */
  263. private PropertyEditor getCustomEditor(String propertyName, Class requiredType) {
  264. CustomEditorHolder holder = (CustomEditorHolder) this.customEditors.get(propertyName);
  265. return (holder != null ? holder.getPropertyEditor(requiredType) : null);
  266. }
  267. /**
  268.  * Get custom editor for the given type. If no direct match found,
  269.  * try custom editor for superclass (which will in any case be able
  270.  * to render a value as String via <code>getAsText</code>).
  271.  * @see java.beans.PropertyEditor#getAsText
  272.  * @return the custom editor, or null if none found for this type
  273.  */
  274. private PropertyEditor getCustomEditor(Class requiredType) {
  275. if (requiredType != null) {
  276. PropertyEditor editor = (PropertyEditor) this.customEditors.get(requiredType);
  277. if (editor == null) {
  278. for (Iterator it = this.customEditors.keySet().iterator(); it.hasNext();) {
  279. Object key = it.next();
  280. if (key instanceof Class && ((Class) key).isAssignableFrom(requiredType)) {
  281. editor = (PropertyEditor) this.customEditors.get(key);
  282. }
  283. }
  284. }
  285. return editor;
  286. }
  287. return null;
  288. }
  289. /**
  290.  * Add property paths with all variations of stripped keys and/or indexes.
  291.  * Invokes itself recursively with nested paths
  292.  * @param strippedPaths the result list to add to
  293.  * @param nestedPath the current nested path
  294.  * @param propertyPath the property path to check for keys/indexes to strip
  295.  */
  296. private void addStrippedPropertyPaths(List strippedPaths, String nestedPath, String propertyPath) {
  297. int startIndex = propertyPath.indexOf(PROPERTY_KEY_PREFIX_CHAR);
  298. if (startIndex != -1) {
  299. int endIndex = propertyPath.indexOf(PROPERTY_KEY_SUFFIX_CHAR);
  300. if (endIndex != -1) {
  301. String prefix = propertyPath.substring(0, startIndex);
  302. String key = propertyPath.substring(startIndex, endIndex + 1);
  303. String suffix = propertyPath.substring(endIndex + 1, propertyPath.length());
  304. // strip the first key
  305. strippedPaths.add(nestedPath + prefix + suffix);
  306. // search for further keys to strip, with the first key stripped
  307. addStrippedPropertyPaths(strippedPaths, nestedPath + prefix, suffix);
  308. // search for further keys to strip, with the first key not stripped
  309. addStrippedPropertyPaths(strippedPaths, nestedPath + prefix + key, suffix);
  310. }
  311. }
  312. }
  313. /**
  314.  * Determine the first respectively last nested property separator in
  315.  * the given property path, ignoring dots in keys (like "map[my.key]").
  316.  * @param propertyPath the property path to check
  317.  * @param last whether to return the last separator rather than the first
  318.  * @return the index of the nested property separator, or -1 if none
  319.  */
  320. private int getNestedPropertySeparatorIndex(String propertyPath, boolean last) {
  321. boolean inKey = false;
  322. int i = (last ? propertyPath.length()-1 : 0);
  323. while ((last && i >= 0) || i < propertyPath.length()) {
  324. switch (propertyPath.charAt(i)) {
  325. case PROPERTY_KEY_PREFIX_CHAR:
  326. case PROPERTY_KEY_SUFFIX_CHAR:
  327. inKey = !inKey;
  328. break;
  329. case NESTED_PROPERTY_SEPARATOR_CHAR:
  330. if (!inKey) {
  331. return i;
  332. }
  333. }
  334. if (last) i--; else i++;
  335. }
  336. return -1;
  337. }
  338. /**
  339.  * Get the last component of the path. Also works if not nested.
  340.  * @param bw BeanWrapper to work on
  341.  * @param nestedPath property path we know is nested
  342.  * @return last component of the path (the property on the target bean)
  343.  */
  344. private String getFinalPath(BeanWrapper bw, String nestedPath) {
  345. if (bw == this) {
  346. return nestedPath;
  347. }
  348. return nestedPath.substring(getNestedPropertySeparatorIndex(nestedPath, true) + 1);
  349. }
  350. /**
  351.  * Recursively navigate to return a BeanWrapper for the nested property path.
  352.  * @param propertyPath property property path, which may be nested
  353.  * @return a BeanWrapper for the target bean
  354.  */
  355. protected BeanWrapperImpl getBeanWrapperForPropertyPath(String propertyPath) throws BeansException {
  356. int pos = getNestedPropertySeparatorIndex(propertyPath, false);
  357. // handle nested properties recursively
  358. if (pos > -1) {
  359. String nestedProperty = propertyPath.substring(0, pos);
  360. String nestedPath = propertyPath.substring(pos + 1);
  361. BeanWrapperImpl nestedBw = getNestedBeanWrapper(nestedProperty);
  362. return nestedBw.getBeanWrapperForPropertyPath(nestedPath);
  363. }
  364. else {
  365. return this;
  366. }
  367. }
  368. /**
  369.  * Retrieve a BeanWrapper for the given nested property.
  370.  * Create a new one if not found in the cache.
  371.  * <p>Note: Caching nested BeanWrappers is necessary now,
  372.  * to keep registered custom editors for nested properties.
  373.  * @param nestedProperty property to create the BeanWrapper for
  374.  * @return the BeanWrapper instance, either cached or newly created
  375.  */
  376. private BeanWrapperImpl getNestedBeanWrapper(String nestedProperty) throws BeansException {
  377. if (this.nestedBeanWrappers == null) {
  378. this.nestedBeanWrappers = new HashMap();
  379. }
  380. // get value of bean property
  381. String[] tokens = getPropertyNameTokens(nestedProperty);
  382. Object propertyValue = getPropertyValue(tokens[0], tokens[1], tokens[2]);
  383. String canonicalName = tokens[0];
  384. String propertyName = tokens[1];
  385. if (propertyValue == null) {
  386. throw new NullValueInNestedPathException(getWrappedClass(), this.nestedPath + canonicalName);
  387. }
  388. // lookup cached sub-BeanWrapper, create new one if not found
  389. BeanWrapperImpl nestedBw = (BeanWrapperImpl) this.nestedBeanWrappers.get(canonicalName);
  390. if (nestedBw == null || nestedBw.getWrappedInstance() != propertyValue) {
  391. if (logger.isDebugEnabled()) {
  392. logger.debug("Creating new nested BeanWrapper for property '" + canonicalName + "'");
  393. }
  394. nestedBw = new BeanWrapperImpl(
  395. propertyValue, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR, this);
  396. // inherit all type-specific PropertyEditors
  397. if (this.customEditors != null) {
  398. for (Iterator it = this.customEditors.entrySet().iterator(); it.hasNext();) {
  399. Map.Entry entry = (Map.Entry) it.next();
  400. if (entry.getKey() instanceof Class) {
  401. Class requiredType = (Class) entry.getKey();
  402. PropertyEditor editor = (PropertyEditor) entry.getValue();
  403. nestedBw.registerCustomEditor(requiredType, editor);
  404. }
  405. else if (entry.getKey() instanceof String) {
  406. String editorPath = (String) entry.getKey();
  407. int pos = getNestedPropertySeparatorIndex(editorPath, false);
  408. if (pos != -1) {
  409. String editorNestedProperty = editorPath.substring(0, pos);
  410. String editorNestedPath = editorPath.substring(pos + 1);
  411. if (editorNestedProperty.equals(canonicalName) || editorNestedProperty.equals(propertyName)) {
  412. CustomEditorHolder editorHolder = (CustomEditorHolder) entry.getValue();
  413. nestedBw.registerCustomEditor(
  414. editorHolder.getRegisteredType(), editorNestedPath, editorHolder.getPropertyEditor());
  415. }
  416. }
  417. }
  418. }
  419. }
  420. this.nestedBeanWrappers.put(canonicalName, nestedBw);
  421. }
  422. else {
  423. if (logger.isDebugEnabled()) {
  424. logger.debug("Using cached nested BeanWrapper for property '" + canonicalName + "'");
  425. }
  426. }
  427. return nestedBw;
  428. }
  429. private String[] getPropertyNameTokens(String propertyName) {
  430. String actualName = propertyName;
  431. String key = null;
  432. int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX);
  433. if (keyStart != -1 && propertyName.endsWith(PROPERTY_KEY_SUFFIX)) {
  434. actualName = propertyName.substring(0, keyStart);
  435. key = propertyName.substring(keyStart + 1, propertyName.length() - 1);
  436. if (key.startsWith("'") && key.endsWith("'")) {
  437. key = key.substring(1, key.length() - 1);
  438. }
  439. else if (key.startsWith(""") && key.endsWith(""")) {
  440. key = key.substring(1, key.length() - 1);
  441. }
  442. }
  443. String canonicalName = actualName;
  444. if (key != null) {
  445. canonicalName += PROPERTY_KEY_PREFIX + key + PROPERTY_KEY_SUFFIX;
  446. }
  447. return new String[] {canonicalName, actualName, key};
  448. }
  449. public Object getPropertyValue(String propertyName) throws BeansException {
  450. BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName);
  451. String[] tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
  452. return nestedBw.getPropertyValue(tokens[0], tokens[1], tokens[2]);
  453. }
  454. protected Object getPropertyValue(String propertyName, String actualName, String key) throws BeansException {
  455. PropertyDescriptor pd = getPropertyDescriptorInternal(actualName);
  456. if (pd == null || pd.getReadMethod() == null) {
  457. throw new NotReadablePropertyException(getWrappedClass(), this.nestedPath + propertyName);
  458. }
  459. if (logger.isDebugEnabled())
  460. logger.debug("About to invoke read method [" + pd.getReadMethod() + "] on object of class [" +
  461. this.object.getClass().getName() + "]");
  462. try {
  463. Object value = pd.getReadMethod().invoke(this.object, null);
  464. if (key != null) {
  465. if (value == null) {
  466. throw new NullValueInNestedPathException(
  467. getWrappedClass(), this.nestedPath + propertyName,
  468. "Cannot access indexed value of property referenced in indexed " +
  469. "property path '" + propertyName + "': returned null");
  470. }
  471. else if (value.getClass().isArray()) {
  472. return Array.get(value, Integer.parseInt(key));
  473. }
  474. else if (value instanceof List) {
  475. List list = (List) value;
  476. return list.get(Integer.parseInt(key));
  477. }
  478. else if (value instanceof Set) {
  479. // apply index to Iterator in case of a Set
  480. Set set = (Set) value;
  481. int index = Integer.parseInt(key);
  482. Iterator it = set.iterator();
  483. for (int i = 0; it.hasNext(); i++) {
  484. Object elem = it.next();
  485. if (i == index) {
  486. return elem;
  487. }
  488. }
  489. throw new InvalidPropertyException(
  490. getWrappedClass(), this.nestedPath + propertyName,
  491. "Cannot get element with index " + index + " from Set of size " +
  492. set.size() + ", accessed using property path '" + propertyName + "'");
  493. }
  494. else if (value instanceof Map) {
  495. Map map = (Map) value;
  496. return map.get(key);
  497. }
  498. else {
  499. throw new InvalidPropertyException(
  500. getWrappedClass(), this.nestedPath + propertyName,
  501. "Property referenced in indexed property path '" + propertyName +
  502. "' is neither an array nor a List nor a Map; returned value was [" + value + "]");
  503. }
  504. }
  505. else {
  506. return value;
  507. }
  508. }
  509. catch (InvocationTargetException ex) {
  510. throw new InvalidPropertyException(
  511. getWrappedClass(), this.nestedPath + propertyName,
  512. "Getter for property '" + actualName + "' threw exception", ex);
  513. }
  514. catch (IllegalAccessException ex) {
  515. throw new InvalidPropertyException(
  516. getWrappedClass(), this.nestedPath + propertyName,
  517. "Illegal attempt to get property '" + actualName + "' threw exception", ex);
  518. }
  519. catch (IndexOutOfBoundsException ex) {
  520. throw new InvalidPropertyException(
  521. getWrappedClass(), this.nestedPath + propertyName,
  522. "Index of out of bounds in property path '" + propertyName + "'", ex);
  523. }
  524. catch (NumberFormatException ex) {
  525. throw new InvalidPropertyException(
  526. getWrappedClass(), this.nestedPath + propertyName,
  527. "Invalid index in property path '" + propertyName + "'", ex);
  528. }
  529. }
  530. public void setPropertyValue(String propertyName, Object value) throws BeansException {
  531. BeanWrapperImpl nestedBw = null;
  532. try {
  533. nestedBw = getBeanWrapperForPropertyPath(propertyName);
  534. }
  535. catch (NotReadablePropertyException ex) {
  536. throw new NotWritablePropertyException(
  537. getWrappedClass(), this.nestedPath + propertyName,
  538. "Nested property in path '" + propertyName + "' does not exist", ex);
  539. }
  540. String[] tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName));
  541. nestedBw.setPropertyValue(tokens[0], tokens[1], tokens[2], value);
  542. }
  543. protected void setPropertyValue(String propertyName, String actualName, String key, Object value)
  544. throws BeansException {
  545. if (key != null) {
  546. Object propValue = null;
  547. try {
  548. propValue = getPropertyValue(actualName);
  549. }
  550. catch (NotReadablePropertyException ex) {
  551. throw new NotWritablePropertyException(
  552. getWrappedClass(), this.nestedPath + propertyName,
  553. "Cannot access indexed value in property referenced " +
  554. "in indexed property path '" + propertyName + "'", ex);
  555. }
  556. if (propValue == null) {
  557. throw new NullValueInNestedPathException(
  558. getWrappedClass(), this.nestedPath + propertyName,
  559. "Cannot access indexed value in property referenced " +
  560. "in indexed property path '" + propertyName + "': returned null");
  561. }
  562. else if (propValue.getClass().isArray()) {
  563. Class requiredType = propValue.getClass().getComponentType();
  564. Object newValue = doTypeConversionIfNecessary(propertyName, propertyName, null, value, requiredType);
  565. try {
  566. Array.set(propValue, Integer.parseInt(key), newValue);
  567. }
  568. catch (IllegalArgumentException ex) {
  569. PropertyChangeEvent pce = new PropertyChangeEvent(
  570. this.object, this.nestedPath + propertyName, null, newValue);
  571. throw new TypeMismatchException(pce, requiredType, ex);
  572. }
  573. catch (IndexOutOfBoundsException ex) {
  574. throw new InvalidPropertyException(
  575. getWrappedClass(), this.nestedPath + propertyName,
  576. "Invalid array index in property path '" + propertyName + "'", ex);
  577. }
  578. }
  579. else if (propValue instanceof List) {
  580. Object newValue = doTypeConversionIfNecessary(propertyName, propertyName, null, value, null);
  581. List list = (List) propValue;
  582. int index = Integer.parseInt(key);
  583. if (index < list.size()) {
  584. list.set(index, newValue);
  585. }
  586. else if (index >= list.size()) {
  587. for (int i = list.size(); i < index; i++) {
  588. try {
  589. list.add(null);
  590. }
  591. catch (NullPointerException ex) {
  592. throw new InvalidPropertyException(
  593. getWrappedClass(), this.nestedPath + propertyName,
  594. "Cannot set element with index " + index + " in List of size " +
  595. list.size() + ", accessed using property path '" + propertyName +
  596. "': List does not support filling up gaps with null elements");
  597. }
  598. }
  599. list.add(newValue);
  600. }
  601. }
  602. else if (propValue instanceof Map) {
  603. Object newValue = doTypeConversionIfNecessary(propertyName, propertyName, null, value, null);
  604. Map map = (Map) propValue;
  605. map.put(key, newValue);
  606. }
  607. else {
  608. throw new InvalidPropertyException(
  609. getWrappedClass(), this.nestedPath + propertyName,
  610. "Property referenced in indexed property path '" + propertyName +
  611. "' is neither an array nor a List nor a Map; returned value was [" + value + "]");
  612. }
  613. }
  614. else {
  615. if (!isWritableProperty(propertyName)) {
  616. throw new NotWritablePropertyException(getWrappedClass(), this.nestedPath + propertyName);
  617. }
  618. PropertyDescriptor pd = getPropertyDescriptor(propertyName);
  619. Method writeMethod = pd.getWriteMethod();
  620. Object newValue = null;
  621. try {
  622. // old value may still be null
  623. newValue = doTypeConversionIfNecessary(propertyName, propertyName, null, value, pd.getPropertyType());
  624. if (pd.getPropertyType().isPrimitive() && (newValue == null || "".equals(newValue))) {
  625. throw new IllegalArgumentException("Invalid value [" + value + "] for property '" +
  626. pd.getName() + "' of primitive type [" + pd.getPropertyType() + "]");
  627. }
  628. if (logger.isDebugEnabled()) {
  629. logger.debug("About to invoke write method [" + writeMethod + "] on object of class [" +
  630. this.object.getClass().getName() + "]");
  631. }
  632. writeMethod.invoke(this.object, new Object[] { newValue });
  633. if (logger.isDebugEnabled()) {
  634. String msg = "Invoked write method [" + writeMethod + "] with value ";
  635. // only cause toString invocation of new value in case of simple property
  636. if (newValue == null || BeanUtils.isSimpleProperty(pd.getPropertyType())) {
  637. logger.debug(msg + PROPERTY_KEY_PREFIX + newValue + PROPERTY_KEY_SUFFIX);
  638. }
  639. else {
  640. logger.debug(msg + "of type [" + pd.getPropertyType().getName() + "]");
  641. }
  642. }
  643. }
  644. catch (InvocationTargetException ex) {
  645. PropertyChangeEvent propertyChangeEvent =
  646. new PropertyChangeEvent(this.object, this.nestedPath + propertyName, null, value);
  647. if (ex.getTargetException() instanceof ClassCastException) {
  648. throw new TypeMismatchException(propertyChangeEvent, pd.getPropertyType(), ex.getTargetException());
  649. }
  650. else {
  651. throw new MethodInvocationException(propertyChangeEvent, ex.getTargetException());
  652. }
  653. }
  654. catch (IllegalArgumentException ex) {
  655. PropertyChangeEvent pce =
  656. new PropertyChangeEvent(this.object, this.nestedPath + propertyName, null, value);
  657. throw new TypeMismatchException(pce, pd.getPropertyType(), ex);
  658. }
  659. catch (IllegalAccessException ex) {
  660. PropertyChangeEvent pce =
  661. new PropertyChangeEvent(this.object, this.nestedPath + propertyName, null, value);
  662. throw new MethodInvocationException(pce, ex);
  663. }
  664. }
  665. }
  666. public void setPropertyValue(PropertyValue pv) throws BeansException {
  667. setPropertyValue(pv.getName(), pv.getValue());
  668. }
  669. /**
  670.  * Bulk update from a Map.
  671.  * Bulk updates from PropertyValues are more powerful: this method is
  672.  * provided for convenience.
  673.  * @param map map containing properties to set, as name-value pairs.
  674.  * The map may include nested properties.
  675.  * @throws BeansException if there's a fatal, low-level exception
  676.  */
  677. public void setPropertyValues(Map map) throws BeansException {
  678. setPropertyValues(new MutablePropertyValues(map));
  679. }
  680. public void setPropertyValues(PropertyValues pvs) throws BeansException {
  681. setPropertyValues(pvs, false);
  682. }
  683. public void setPropertyValues(PropertyValues propertyValues, boolean ignoreUnknown) throws BeansException {
  684. List propertyAccessExceptions = new ArrayList();
  685. PropertyValue[] pvs = propertyValues.getPropertyValues();
  686. for (int i = 0; i < pvs.length; i++) {
  687. try {
  688. // This method may throw any BeansException, which won't be caught
  689. // here, if there is a critical failure such as no matching field.
  690. // We can attempt to deal only with less serious exceptions.
  691. setPropertyValue(pvs[i]);
  692. }
  693. catch (NotWritablePropertyException ex) {
  694. if (!ignoreUnknown) {
  695. throw ex;
  696. }
  697. // otherwise, just ignore it and continue...
  698. }
  699. catch (PropertyAccessException ex) {
  700. propertyAccessExceptions.add(ex);
  701. }
  702. }
  703. // If we encountered individual exceptions, throw the composite exception.
  704. if (!propertyAccessExceptions.isEmpty()) {
  705. Object[] paeArray = propertyAccessExceptions.toArray(
  706. new PropertyAccessException[propertyAccessExceptions.size()]);
  707. throw new PropertyAccessExceptionsException(this, (PropertyAccessException[]) paeArray);
  708. }
  709. }
  710. private PropertyChangeEvent createPropertyChangeEvent(String propertyName, Object oldValue, Object newValue) {
  711. return new PropertyChangeEvent(
  712. (this.object != null ? this.object : "constructor"),
  713. (propertyName != null ? this.nestedPath + propertyName : null),
  714. oldValue, newValue);
  715. }
  716. /**
  717.  * Convert the value to the required type (if necessary from a String).
  718.  * Conversions from String to any type use the setAsText method of
  719.  * the PropertyEditor class. Note that a PropertyEditor must be registered
  720.  * for this class for this to work. This is a standard Java Beans API.
  721.  * A number of property editors are automatically registered by this class.
  722.  * @param newValue proposed change value.
  723.  * @param requiredType type we must convert to
  724.  * @throws BeansException if there is an internal error
  725.  * @return new value, possibly the result of type convertion
  726.  */
  727. public Object doTypeConversionIfNecessary(Object newValue, Class requiredType) throws BeansException {
  728. return doTypeConversionIfNecessary(null, null, null, newValue, requiredType);
  729. }
  730. /**
  731.  * Convert the value to the required type (if necessary from a String),
  732.  * for the specified property.
  733.  * @param propertyName name of the property
  734.  * @param oldValue previous value, if available (may be null)
  735.  * @param newValue proposed change value.
  736.  * @param requiredType type we must convert to
  737.  * @throws BeansException if there is an internal error
  738.  * @return converted value (i.e. possibly the result of type conversion)
  739.  */
  740. protected Object doTypeConversionIfNecessary(String propertyName, String fullPropertyName,
  741.  Object oldValue, Object newValue,
  742.  Class requiredType) throws BeansException {
  743. Object convertedValue = newValue;
  744. if (convertedValue != null) {
  745. // custom editor for this type?
  746. PropertyEditor pe = findCustomEditor(requiredType, fullPropertyName);
  747. // value not of required type?
  748. if (pe != null ||
  749. (requiredType != null &&
  750.  (requiredType.isArray() || !requiredType.isAssignableFrom(convertedValue.getClass())))) {
  751. if (pe == null && requiredType != null) {
  752. // no custom editor -> check BeanWrapperImpl's default editors
  753. pe = (PropertyEditor) this.defaultEditors.get(requiredType);
  754. if (pe == null) {
  755. // no BeanWrapper default editor -> check standard JavaBean editors
  756. pe = PropertyEditorManager.findEditor(requiredType);
  757. }
  758. }
  759. if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) {
  760. if (logger.isDebugEnabled()) {
  761. logger.debug("Converting String array to comma-delimited String [" + convertedValue + "]");
  762. }
  763. convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue);
  764. }
  765. if (pe != null) {
  766. if (convertedValue instanceof String) {
  767. // use PropertyEditor's setAsText in case of a String value
  768. if (logger.isDebugEnabled()) {
  769. logger.debug("Converting String to [" + requiredType + "] using property editor [" + pe + "]");
  770. }
  771. try {
  772. pe.setAsText((String) convertedValue);
  773. convertedValue = pe.getValue();
  774. }
  775. catch (IllegalArgumentException ex) {
  776. throw new TypeMismatchException(
  777. createPropertyChangeEvent(fullPropertyName, oldValue, newValue), requiredType, ex);
  778. }
  779. }
  780. else {
  781. // Not a String -> use PropertyEditor's setValue.
  782. // With standard PropertyEditors, this will return the very same object;
  783. // we just want to allow special PropertyEditors to override setValue
  784. // for type conversion from non-String values to the required type.
  785. try {
  786. pe.setValue(convertedValue);
  787. convertedValue = pe.getValue();
  788. }
  789. catch (IllegalArgumentException ex) {
  790. throw new TypeMismatchException(
  791. createPropertyChangeEvent(fullPropertyName, oldValue, newValue), requiredType, ex);
  792. }
  793. }
  794. }
  795. // array required -> apply appropriate conversion of elements
  796. if (requiredType != null && requiredType.isArray()) {
  797. Class componentType = requiredType.getComponentType();
  798. if (convertedValue instanceof Collection) {
  799. // convert individual elements to array elements
  800. Collection coll = (Collection) convertedValue;
  801. Object result = Array.newInstance(componentType, coll.size());
  802. int i = 0;
  803. for (Iterator it = coll.iterator(); it.hasNext(); i++) {
  804. Object value = doTypeConversionIfNecessary(
  805. propertyName, propertyName + PROPERTY_KEY_PREFIX + i + PROPERTY_KEY_SUFFIX,
  806. null, it.next(), componentType);
  807. Array.set(result, i, value);
  808. }
  809. return result;
  810. }
  811. else if (convertedValue != null && convertedValue.getClass().isArray()) {
  812. // convert individual elements to array elements
  813. int arrayLength = Array.getLength(convertedValue);
  814. Object result = Array.newInstance(componentType, arrayLength);
  815. for (int i = 0; i < arrayLength; i++) {
  816. Object value = doTypeConversionIfNecessary(
  817. propertyName, propertyName + PROPERTY_KEY_PREFIX + i + PROPERTY_KEY_SUFFIX,
  818. null, Array.get(convertedValue, i), componentType);
  819. Array.set(result, i, value);
  820. }
  821. return result;
  822. }
  823. else {
  824. // a plain value: convert it to an array with a single component
  825. Object result = Array.newInstance(componentType, 1) ;
  826. Object val = doTypeConversionIfNecessary(
  827. propertyName, propertyName + PROPERTY_KEY_PREFIX + 0 + PROPERTY_KEY_SUFFIX,
  828. null, convertedValue, componentType);
  829. Array.set(result, 0, val);
  830. return result;
  831. }
  832. }
  833. }
  834. // Throw explicit TypeMismatchException with full context information
  835. // if the resulting value definitely doesn't match the required type.
  836. if (convertedValue != null && requiredType != null && !requiredType.isPrimitive() &&
  837. !requiredType.isAssignableFrom(convertedValue.getClass())) {
  838. throw new TypeMismatchException(
  839. createPropertyChangeEvent(fullPropertyName, oldValue, newValue), requiredType);
  840. }
  841. }
  842. return convertedValue;
  843. }
  844. public PropertyDescriptor[] getPropertyDescriptors() {
  845. return this.cachedIntrospectionResults.getBeanInfo().getPropertyDescriptors();
  846. }
  847. public PropertyDescriptor getPropertyDescriptor(String propertyName) throws BeansException {
  848. if (propertyName == null) {
  849. throw new IllegalArgumentException("Can't find property descriptor for null property");
  850. }
  851. PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
  852. if (pd != null) {
  853. return pd;
  854. }
  855. else {
  856. throw new InvalidPropertyException(getWrappedClass(), this.nestedPath + propertyName,
  857.  "No property '" + propertyName + "' found");
  858. }
  859. }
  860. /**
  861.  * Internal version of getPropertyDescriptor:
  862.  * Returns null if not found rather than throwing an exception.
  863.  */
  864. protected PropertyDescriptor getPropertyDescriptorInternal(String propertyName) throws BeansException {
  865. BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName);
  866. return nestedBw.cachedIntrospectionResults.getPropertyDescriptor(getFinalPath(nestedBw, propertyName));
  867. }
  868. public Class getPropertyType(String propertyName) throws BeansException {
  869. try {
  870. PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
  871. if (pd != null) {
  872. return pd.getPropertyType();
  873. }
  874. else {
  875. // maybe an indexed/mapped property
  876. Object value = getPropertyValue(propertyName);
  877. if (value != null) {
  878. return value.getClass();
  879. }
  880. }
  881. }
  882. catch (InvalidPropertyException ex) {
  883. // consider as not determinable
  884. }
  885. return null;
  886. }
  887. public boolean isReadableProperty(String propertyName) {
  888. // This is a programming error, although asking for a property
  889. // that doesn't exist is not.
  890. if (propertyName == null) {
  891. throw new IllegalArgumentException("Can't find readability status for null property");
  892. }
  893. try {
  894. PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
  895. if (pd != null) {
  896. if (pd.getReadMethod() != null) {
  897. return true;
  898. }
  899. }
  900. else {
  901. // maybe an indexed/mapped property
  902. getPropertyValue(propertyName);
  903. return true;
  904. }
  905. }
  906. catch (InvalidPropertyException ex) {
  907. // cannot be evaluated, so can't be readable
  908. }
  909. return false;
  910. }
  911. public boolean isWritableProperty(String propertyName) {
  912. // This is a programming error, although asking for a property
  913. // that doesn't exist is not.
  914. if (propertyName == null) {
  915. throw new IllegalArgumentException("Can't find writability status for null property");
  916. }
  917. try {
  918. PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
  919. if (pd != null) {
  920. if (pd.getWriteMethod() != null) {
  921. return true;
  922. }
  923. }
  924. else {
  925. // maybe an indexed/mapped property
  926. getPropertyValue(propertyName);
  927. return true;
  928. }
  929. }
  930. catch (InvalidPropertyException ex) {
  931. // cannot be evaluated, so can't be writable
  932. }
  933. return false;
  934. }
  935. //---------------------------------------------------------------------
  936. // Diagnostics
  937. //---------------------------------------------------------------------
  938. /**
  939.  * This method is expensive! Only call for diagnostics and debugging reasons,
  940.  * not in production.
  941.  * @return a string describing the state of this object
  942.  */
  943. public String toString() {
  944. StringBuffer sb = new StringBuffer();
  945. try {
  946. sb.append("BeanWrapperImpl: wrapping class [" + getWrappedClass().getName() + "]; ");
  947. PropertyDescriptor pds[] = getPropertyDescriptors();
  948. if (pds != null) {
  949. for (int i = 0; i < pds.length; i++) {
  950. Object val = getPropertyValue(pds[i].getName());
  951. String valStr = (val != null) ? val.toString() : "null";
  952. sb.append(pds[i].getName() + "={" + valStr + "}");
  953. }
  954. }
  955. }
  956. catch (Exception ex) {
  957. sb.append("exception encountered: " + ex);
  958. }
  959. return sb.toString();
  960. }
  961. /**
  962.  * Holder for a registered custom editor with property name.
  963.  * Keeps the PropertyEditor itself plus the type it was registered for.
  964.  */
  965. private static class CustomEditorHolder {
  966. private final PropertyEditor propertyEditor;
  967. private final Class registeredType;
  968. private CustomEditorHolder(PropertyEditor propertyEditor, Class registeredType) {
  969. this.propertyEditor = propertyEditor;
  970. this.registeredType = registeredType;
  971. }
  972. private PropertyEditor getPropertyEditor() {
  973. return propertyEditor;
  974. }
  975. private Class getRegisteredType() {
  976. return registeredType;
  977. }
  978. private PropertyEditor getPropertyEditor(Class requiredType) {
  979. // Special case: If no required type specified, which usually only happens for
  980. // Collection elements, or required type is not assignable to registered type,
  981. // which usually only happens for generic properties of type Object -
  982. // then return PropertyEditor if not registered for Collection or array type.
  983. // (If not registered for Collection or array, it is assumed to be intended
  984. // for elements.)
  985. if (this.registeredType == null ||
  986. (requiredType != null &&
  987.     (BeanUtils.isAssignable(this.registeredType, requiredType) ||
  988.     BeanUtils.isAssignable(requiredType, this.registeredType))) ||
  989. (requiredType == null &&
  990.     (!Collection.class.isAssignableFrom(this.registeredType) && !this.registeredType.isArray()))) {
  991. return this.propertyEditor;
  992. }
  993. else {
  994. return null;
  995. }
  996. }
  997. }
  998. }