1 16 17 package org.springframework.beans; 18 19 import java.beans.PropertyChangeEvent ; 20 import java.beans.PropertyDescriptor ; 21 import java.lang.reflect.Array ; 22 import java.lang.reflect.InvocationTargetException ; 23 import java.lang.reflect.Method ; 24 import java.lang.reflect.Modifier ; 25 import java.util.ArrayList ; 26 import java.util.HashMap ; 27 import java.util.Iterator ; 28 import java.util.List ; 29 import java.util.Map ; 30 import java.util.Set ; 31 32 import org.apache.commons.logging.Log; 33 import org.apache.commons.logging.LogFactory; 34 35 import org.springframework.core.GenericCollectionTypeResolver; 36 import org.springframework.core.JdkVersion; 37 import org.springframework.core.MethodParameter; 38 import org.springframework.util.Assert; 39 import org.springframework.util.ObjectUtils; 40 import org.springframework.util.StringUtils; 41 42 73 public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWrapper { 74 75 78 private static final Log logger = LogFactory.getLog(BeanWrapperImpl.class); 79 80 81 82 private Object object; 83 84 private String nestedPath = ""; 85 86 private Object rootObject; 87 88 private TypeConverterDelegate typeConverterDelegate; 89 90 94 private CachedIntrospectionResults cachedIntrospectionResults; 95 96 99 private Map nestedBeanWrappers; 100 101 102 107 public BeanWrapperImpl() { 108 this(true); 109 } 110 111 117 public BeanWrapperImpl(boolean registerDefaultEditors) { 118 if (registerDefaultEditors) { 119 registerDefaultEditors(); 120 } 121 this.typeConverterDelegate = new TypeConverterDelegate(this); 122 } 123 124 128 public BeanWrapperImpl(Object object) { 129 registerDefaultEditors(); 130 setWrappedInstance(object); 131 } 132 133 137 public BeanWrapperImpl(Class clazz) { 138 registerDefaultEditors(); 139 setWrappedInstance(BeanUtils.instantiateClass(clazz)); 140 } 141 142 149 public BeanWrapperImpl(Object object, String nestedPath, Object rootObject) { 150 registerDefaultEditors(); 151 setWrappedInstance(object, nestedPath, rootObject); 152 } 153 154 161 private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl superBw) { 162 setWrappedInstance(object, nestedPath, superBw.getWrappedInstance()); 163 setExtractOldValueForEditor(superBw.isExtractOldValueForEditor()); 164 } 165 166 167 171 176 public void setWrappedInstance(Object object) { 177 setWrappedInstance(object, "", null); 178 } 179 180 187 public void setWrappedInstance(Object object, String nestedPath, Object rootObject) { 188 Assert.notNull(object, "Bean object must not be null"); 189 this.object = object; 190 this.nestedPath = (nestedPath != null ? nestedPath : ""); 191 this.rootObject = (!"".equals(this.nestedPath) ? rootObject : object); 192 this.nestedBeanWrappers = null; 193 this.typeConverterDelegate = new TypeConverterDelegate(this, object); 194 setIntrospectionClass(object.getClass()); 195 } 196 197 public final Object getWrappedInstance() { 198 return this.object; 199 } 200 201 public final Class getWrappedClass() { 202 return (this.object != null ? this.object.getClass() : null); 203 } 204 205 208 public final String getNestedPath() { 209 return this.nestedPath; 210 } 211 212 216 public final Object getRootInstance() { 217 return this.rootObject; 218 } 219 220 224 public final Class getRootClass() { 225 return (this.rootObject != null ? this.rootObject.getClass() : null); 226 } 227 228 233 protected void setIntrospectionClass(Class clazz) { 234 if (this.cachedIntrospectionResults == null || 235 !this.cachedIntrospectionResults.getBeanClass().equals(clazz)) { 236 this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(clazz); 237 } 238 } 239 240 241 public PropertyDescriptor [] getPropertyDescriptors() { 242 Assert.state(this.cachedIntrospectionResults != null, "BeanWrapper does not hold a bean instance"); 243 return this.cachedIntrospectionResults.getBeanInfo().getPropertyDescriptors(); 244 } 245 246 public PropertyDescriptor getPropertyDescriptor(String propertyName) throws BeansException { 247 PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); 248 if (pd == null) { 249 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 250 "No property '" + propertyName + "' found"); 251 } 252 return pd; 253 } 254 255 263 protected PropertyDescriptor getPropertyDescriptorInternal(String propertyName) throws BeansException { 264 Assert.state(this.cachedIntrospectionResults != null, "BeanWrapper does not hold a bean instance"); 265 Assert.notNull(propertyName, "Property name must not be null"); 266 BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName); 267 return nestedBw.cachedIntrospectionResults.getPropertyDescriptor(getFinalPath(nestedBw, propertyName)); 268 } 269 270 public Class getPropertyType(String propertyName) throws BeansException { 271 try { 272 PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); 273 if (pd != null) { 274 return pd.getPropertyType(); 275 } 276 else { 277 Object value = getPropertyValue(propertyName); 279 if (value != null) { 280 return value.getClass(); 281 } 282 Class editorType = guessPropertyTypeFromEditors(propertyName); 285 if (editorType != null) { 286 return editorType; 287 } 288 } 289 } 290 catch (InvalidPropertyException ex) { 291 } 293 return null; 294 } 295 296 public boolean isReadableProperty(String propertyName) { 297 try { 298 PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); 299 if (pd != null) { 300 if (pd.getReadMethod() != null) { 301 return true; 302 } 303 } 304 else { 305 getPropertyValue(propertyName); 307 return true; 308 } 309 } 310 catch (InvalidPropertyException ex) { 311 } 313 return false; 314 } 315 316 public boolean isWritableProperty(String propertyName) { 317 try { 318 PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); 319 if (pd != null) { 320 if (pd.getWriteMethod() != null) { 321 return true; 322 } 323 } 324 else { 325 getPropertyValue(propertyName); 327 return true; 328 } 329 } 330 catch (InvalidPropertyException ex) { 331 } 333 return false; 334 } 335 336 337 341 345 public Object doTypeConversionIfNecessary(Object value, Class requiredType) throws TypeMismatchException { 346 return convertIfNecessary(value, requiredType, null); 347 } 348 349 public Object convertIfNecessary(Object value, Class requiredType) throws TypeMismatchException { 350 return convertIfNecessary(value, requiredType, null); 351 } 352 353 public Object convertIfNecessary( 354 Object value, Class requiredType, MethodParameter methodParam) throws TypeMismatchException { 355 try { 356 return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam); 357 } 358 catch (IllegalArgumentException ex) { 359 throw new TypeMismatchException(value, requiredType, ex); 360 } 361 } 362 363 364 368 374 private String getFinalPath(BeanWrapper bw, String nestedPath) { 375 if (bw == this) { 376 return nestedPath; 377 } 378 return nestedPath.substring(PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(nestedPath) + 1); 379 } 380 381 386 protected BeanWrapperImpl getBeanWrapperForPropertyPath(String propertyPath) { 387 int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath); 388 if (pos > -1) { 390 String nestedProperty = propertyPath.substring(0, pos); 391 String nestedPath = propertyPath.substring(pos + 1); 392 BeanWrapperImpl nestedBw = getNestedBeanWrapper(nestedProperty); 393 return nestedBw.getBeanWrapperForPropertyPath(nestedPath); 394 } 395 else { 396 return this; 397 } 398 } 399 400 408 private BeanWrapperImpl getNestedBeanWrapper(String nestedProperty) { 409 if (this.nestedBeanWrappers == null) { 410 this.nestedBeanWrappers = new HashMap (); 411 } 412 PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty); 414 String canonicalName = tokens.canonicalName; 415 Object propertyValue = getPropertyValue(tokens); 416 if (propertyValue == null) { 417 throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName); 418 } 419 420 BeanWrapperImpl nestedBw = (BeanWrapperImpl) this.nestedBeanWrappers.get(canonicalName); 422 if (nestedBw == null || nestedBw.getWrappedInstance() != propertyValue) { 423 if (logger.isTraceEnabled()) { 424 logger.trace("Creating new nested BeanWrapper for property '" + canonicalName + "'"); 425 } 426 nestedBw = newNestedBeanWrapper(propertyValue, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR); 427 copyDefaultEditorsTo(nestedBw); 429 copyCustomEditorsTo(nestedBw, canonicalName); 430 this.nestedBeanWrappers.put(canonicalName, nestedBw); 431 } 432 else { 433 if (logger.isTraceEnabled()) { 434 logger.trace("Using cached nested BeanWrapper for property '" + canonicalName + "'"); 435 } 436 } 437 return nestedBw; 438 } 439 440 448 protected BeanWrapperImpl newNestedBeanWrapper(Object object, String nestedPath) { 449 return new BeanWrapperImpl(object, nestedPath, this); 450 } 451 452 457 private PropertyTokenHolder getPropertyNameTokens(String propertyName) { 458 PropertyTokenHolder tokens = new PropertyTokenHolder(); 459 String actualName = null; 460 List keys = new ArrayList (2); 461 int searchIndex = 0; 462 while (searchIndex != -1) { 463 int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex); 464 searchIndex = -1; 465 if (keyStart != -1) { 466 int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length()); 467 if (keyEnd != -1) { 468 if (actualName == null) { 469 actualName = propertyName.substring(0, keyStart); 470 } 471 String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd); 472 if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) { 473 key = key.substring(1, key.length() - 1); 474 } 475 keys.add(key); 476 searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length(); 477 } 478 } 479 } 480 tokens.actualName = (actualName != null ? actualName : propertyName); 481 tokens.canonicalName = tokens.actualName; 482 if (!keys.isEmpty()) { 483 tokens.canonicalName += 484 PROPERTY_KEY_PREFIX + 485 StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) + 486 PROPERTY_KEY_SUFFIX; 487 tokens.keys = StringUtils.toStringArray(keys); 488 } 489 return tokens; 490 } 491 492 493 497 public Object getPropertyValue(String propertyName) throws BeansException { 498 BeanWrapperImpl nestedBw = getBeanWrapperForPropertyPath(propertyName); 499 PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName)); 500 return nestedBw.getPropertyValue(tokens); 501 } 502 503 private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException { 504 String propertyName = tokens.canonicalName; 505 String actualName = tokens.actualName; 506 PropertyDescriptor pd = getPropertyDescriptorInternal(tokens.actualName); 507 if (pd == null || pd.getReadMethod() == null) { 508 throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName); 509 } 510 Method readMethod = pd.getReadMethod(); 511 if (logger.isTraceEnabled()) { 512 logger.trace("About to invoke read method [" + readMethod + "] on object of class [" + 513 this.object.getClass().getName() + "]"); 514 } 515 try { 516 if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { 517 readMethod.setAccessible(true); 518 } 519 Object value = readMethod.invoke(this.object, (Object []) null); 520 if (tokens.keys != null) { 521 for (int i = 0; i < tokens.keys.length; i++) { 523 String key = tokens.keys[i]; 524 if (value == null) { 525 throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, 526 "Cannot access indexed value of property referenced in indexed " + 527 "property path '" + propertyName + "': returned null"); 528 } 529 else if (value.getClass().isArray()) { 530 value = Array.get(value, Integer.parseInt(key)); 531 } 532 else if (value instanceof List ) { 533 List list = (List ) value; 534 value = list.get(Integer.parseInt(key)); 535 } 536 else if (value instanceof Set ) { 537 Set set = (Set ) value; 539 int index = Integer.parseInt(key); 540 if (index < 0 || index >= set.size()) { 541 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 542 "Cannot get element with index " + index + " from Set of size " + 543 set.size() + ", accessed using property path '" + propertyName + "'"); 544 } 545 Iterator it = set.iterator(); 546 for (int j = 0; it.hasNext(); j++) { 547 Object elem = it.next(); 548 if (j == index) { 549 value = elem; 550 break; 551 } 552 } 553 } 554 else if (value instanceof Map ) { 555 Map map = (Map ) value; 556 Class mapKeyType = null; 557 if (JdkVersion.isAtLeastJava15()) { 558 mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(pd.getReadMethod(), i + 1); 559 } 560 Object convertedMapKey = this.typeConverterDelegate.convertIfNecessary(key, mapKeyType); 563 value = map.get(convertedMapKey); 566 } 567 else { 568 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 569 "Property referenced in indexed property path '" + propertyName + 570 "' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]"); 571 } 572 } 573 } 574 return value; 575 } 576 catch (InvocationTargetException ex) { 577 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 578 "Getter for property '" + actualName + "' threw exception", ex); 579 } 580 catch (IllegalAccessException ex) { 581 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 582 "Illegal attempt to get property '" + actualName + "' threw exception", ex); 583 } 584 catch (IndexOutOfBoundsException ex) { 585 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 586 "Index of out of bounds in property path '" + propertyName + "'", ex); 587 } 588 catch (NumberFormatException ex) { 589 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 590 "Invalid index in property path '" + propertyName + "'", ex); 591 } 592 } 593 594 public void setPropertyValue(String propertyName, Object value) throws BeansException { 595 BeanWrapperImpl nestedBw = null; 596 try { 597 nestedBw = getBeanWrapperForPropertyPath(propertyName); 598 } 599 catch (NotReadablePropertyException ex) { 600 throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, 601 "Nested property in path '" + propertyName + "' does not exist", ex); 602 } 603 PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedBw, propertyName)); 604 nestedBw.setPropertyValue(tokens, value); 605 } 606 607 private void setPropertyValue(PropertyTokenHolder tokens, Object newValue) throws BeansException { 608 String propertyName = tokens.canonicalName; 609 610 if (tokens.keys != null) { 611 PropertyTokenHolder getterTokens = new PropertyTokenHolder(); 613 getterTokens.canonicalName = tokens.canonicalName; 614 getterTokens.actualName = tokens.actualName; 615 getterTokens.keys = new String [tokens.keys.length - 1]; 616 System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1); 617 Object propValue = null; 618 try { 619 propValue = getPropertyValue(getterTokens); 620 } 621 catch (NotReadablePropertyException ex) { 622 throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, 623 "Cannot access indexed value in property referenced " + 624 "in indexed property path '" + propertyName + "'", ex); 625 } 626 String key = tokens.keys[tokens.keys.length - 1]; 628 if (propValue == null) { 629 throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, 630 "Cannot access indexed value in property referenced " + 631 "in indexed property path '" + propertyName + "': returned null"); 632 } 633 else if (propValue.getClass().isArray()) { 634 Class requiredType = propValue.getClass().getComponentType(); 635 int arrayIndex = Integer.parseInt(key); 636 Object oldValue = null; 637 try { 638 if (isExtractOldValueForEditor()) { 639 oldValue = Array.get(propValue, arrayIndex); 640 } 641 Object convertedValue = this.typeConverterDelegate.convertIfNecessary( 642 propertyName, oldValue, newValue, requiredType); 643 Array.set(propValue, Integer.parseInt(key), convertedValue); 644 } 645 catch (IllegalArgumentException ex) { 646 PropertyChangeEvent pce = 647 new PropertyChangeEvent (this.rootObject, this.nestedPath + propertyName, oldValue, newValue); 648 throw new TypeMismatchException(pce, requiredType, ex); 649 } 650 catch (IndexOutOfBoundsException ex) { 651 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 652 "Invalid array index in property path '" + propertyName + "'", ex); 653 } 654 } 655 else if (propValue instanceof List ) { 656 PropertyDescriptor pd = getPropertyDescriptorInternal(tokens.actualName); 657 Class requiredType = null; 658 if (JdkVersion.isAtLeastJava15()) { 659 requiredType = GenericCollectionTypeResolver.getCollectionReturnType( 660 pd.getReadMethod(), tokens.keys.length); 661 } 662 List list = (List ) propValue; 663 int index = Integer.parseInt(key); 664 Object oldValue = null; 665 if (isExtractOldValueForEditor() && index < list.size()) { 666 oldValue = list.get(index); 667 } 668 try { 669 Object convertedValue = this.typeConverterDelegate.convertIfNecessary( 670 propertyName, oldValue, newValue, requiredType); 671 if (index < list.size()) { 672 list.set(index, convertedValue); 673 } 674 else if (index >= list.size()) { 675 for (int i = list.size(); i < index; i++) { 676 try { 677 list.add(null); 678 } 679 catch (NullPointerException ex) { 680 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 681 "Cannot set element with index " + index + " in List of size " + 682 list.size() + ", accessed using property path '" + propertyName + 683 "': List does not support filling up gaps with null elements"); 684 } 685 } 686 list.add(convertedValue); 687 } 688 } 689 catch (IllegalArgumentException ex) { 690 PropertyChangeEvent pce = 691 new PropertyChangeEvent (this.rootObject, this.nestedPath + propertyName, oldValue, newValue); 692 throw new TypeMismatchException(pce, requiredType, ex); 693 } 694 } 695 else if (propValue instanceof Map ) { 696 PropertyDescriptor pd = getPropertyDescriptorInternal(tokens.actualName); 697 Class mapKeyType = null; 698 Class mapValueType = null; 699 if (JdkVersion.isAtLeastJava15()) { 700 mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType( 701 pd.getReadMethod(), tokens.keys.length); 702 mapValueType = GenericCollectionTypeResolver.getMapValueReturnType( 703 pd.getReadMethod(), tokens.keys.length); 704 } 705 Map map = (Map ) propValue; 706 Object oldValue = null; 707 if (isExtractOldValueForEditor()) { 708 oldValue = map.get(key); 709 } 710 Object convertedMapKey = null; 711 Object convertedMapValue = null; 712 try { 713 convertedMapKey = this.typeConverterDelegate.convertIfNecessary(key, mapKeyType); 716 } 717 catch (IllegalArgumentException ex) { 718 PropertyChangeEvent pce = 719 new PropertyChangeEvent (this.rootObject, this.nestedPath + propertyName, oldValue, newValue); 720 throw new TypeMismatchException(pce, mapKeyType, ex); 721 } 722 try { 723 convertedMapValue = this.typeConverterDelegate.convertIfNecessary( 726 propertyName, oldValue, newValue, mapValueType, null, 727 new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length + 1)); 728 } 729 catch (IllegalArgumentException ex) { 730 PropertyChangeEvent pce = 731 new PropertyChangeEvent (this.rootObject, this.nestedPath + propertyName, oldValue, newValue); 732 throw new TypeMismatchException(pce, mapValueType, ex); 733 } 734 map.put(convertedMapKey, convertedMapValue); 735 } 736 else { 737 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 738 "Property referenced in indexed property path '" + propertyName + 739 "' is neither an array nor a List nor a Map; returned value was [" + newValue + "]"); 740 } 741 } 742 743 else { 744 PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); 745 if (pd == null || pd.getWriteMethod() == null) { 746 PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass()); 747 throw new NotWritablePropertyException( 748 getRootClass(), this.nestedPath + propertyName, 749 matches.buildErrorMessage(), matches.getPossibleMatches()); 750 } 751 752 Method readMethod = pd.getReadMethod(); 753 Method writeMethod = pd.getWriteMethod(); 754 Object oldValue = null; 755 756 if (isExtractOldValueForEditor() && readMethod != null) { 757 if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { 758 readMethod.setAccessible(true); 759 } 760 try { 761 oldValue = readMethod.invoke(this.object, new Object [0]); 762 } 763 catch (Exception ex) { 764 if (logger.isDebugEnabled()) { 765 logger.debug("Could not read previous value of property '" + this.nestedPath + propertyName + "'", ex); 766 } 767 } 768 } 769 770 try { 771 Object convertedValue = this.typeConverterDelegate.convertIfNecessary(oldValue, newValue, pd); 772 773 if (pd.getPropertyType().isPrimitive() && (convertedValue == null || "".equals(convertedValue))) { 774 throw new IllegalArgumentException ("Invalid value [" + newValue + "] for property '" + 775 pd.getName() + "' of primitive type [" + pd.getPropertyType() + "]"); 776 } 777 778 if (logger.isTraceEnabled()) { 779 logger.trace("About to invoke write method [" + writeMethod + "] on object of class [" + 780 this.object.getClass().getName() + "]"); 781 } 782 if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { 783 writeMethod.setAccessible(true); 784 } 785 writeMethod.invoke(this.object, new Object [] {convertedValue}); 786 if (logger.isTraceEnabled()) { 787 logger.trace("Invoked write method [" + writeMethod + "] with value of type [" + 788 pd.getPropertyType().getName() + "]"); 789 } 790 } 791 catch (InvocationTargetException ex) { 792 PropertyChangeEvent propertyChangeEvent = 793 new PropertyChangeEvent (this.rootObject, this.nestedPath + propertyName, oldValue, newValue); 794 if (ex.getTargetException() instanceof ClassCastException ) { 795 throw new TypeMismatchException(propertyChangeEvent, pd.getPropertyType(), ex.getTargetException()); 796 } 797 else { 798 throw new MethodInvocationException(propertyChangeEvent, ex.getTargetException()); 799 } 800 } 801 catch (IllegalArgumentException ex) { 802 PropertyChangeEvent pce = 803 new PropertyChangeEvent (this.rootObject, this.nestedPath + propertyName, oldValue, newValue); 804 throw new TypeMismatchException(pce, pd.getPropertyType(), ex); 805 } 806 catch (IllegalAccessException ex) { 807 PropertyChangeEvent pce = 808 new PropertyChangeEvent (this.rootObject, this.nestedPath + propertyName, oldValue, newValue); 809 throw new MethodInvocationException(pce, ex); 810 } 811 } 812 } 813 814 815 public String toString() { 816 StringBuffer sb = new StringBuffer (getClass().getName()); 817 if (this.object != null) { 818 sb.append(": wrapping object [").append(ObjectUtils.identityToString(this.object)).append("]"); 819 } 820 else { 821 sb.append(": no wrapped object set"); 822 } 823 return sb.toString(); 824 } 825 826 827 831 private static class PropertyTokenHolder { 832 833 public String canonicalName; 834 835 public String actualName; 836 837 public String [] keys; 838 } 839 840 } 841 | Popular Tags |