001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.beanutils; 018 019import java.io.Serializable; 020import java.lang.reflect.Array; 021import java.math.BigDecimal; 022import java.math.BigInteger; 023import java.util.ArrayList; 024import java.util.Date; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031 032/** 033 * <p>DynaBean which automatically adds properties to the <code>DynaClass</code> 034 * and provides <i>Lazy List</i> and <i>Lazy Map</i> features.</p> 035 * 036 * <p>DynaBeans deal with three types of properties - <i>simple</i>, <i>indexed</i> and <i>mapped</i> and 037 * have the following <code>get()</code> and <code>set()</code> methods for 038 * each of these types:</p> 039 * <ul> 040 * <li><i>Simple</i> property methods - <code>get(name)</code> and 041 * <code>set(name, value)</code></li> 042 * <li><i>Indexed</i> property methods - <code>get(name, index)</code> and 043 * <code>set(name, index, value)</code></li> 044 * <li><i>Mapped</i> property methods - <code>get(name, key)</code> and 045 * <code>set(name, key, value)</code></li> 046 * </ul> 047 * 048 * <p><b><u>Getting Property Values</u></b></p> 049 * <p>Calling any of the <code>get()</code> methods, for a property which 050 * doesn't exist, returns <code>null</code> in this implementation.</p> 051 * 052 * <p><b><u>Setting Simple Properties</u></b></p> 053 * <p>The <code>LazyDynaBean</code> will automatically add a property to the <code>DynaClass</code> 054 * if it doesn't exist when the <code>set(name, value)</code> method is called.</p> 055 * 056 * <pre><code> 057 * DynaBean myBean = new LazyDynaBean(); 058 * myBean.set("myProperty", "myValue"); 059 * </code></pre> 060 * 061 * <p><b><u>Setting Indexed Properties</u></b></p> 062 * <p>If the property <b>doesn't</b> exist, the <code>LazyDynaBean</code> will automatically add 063 * a property with an <code>ArrayList</code> type to the <code>DynaClass</code> when 064 * the <code>set(name, index, value)</code> method is called. 065 * It will also instantiate a new <code>ArrayList</code> and automatically <i>grow</i> 066 * the <code>List</code> so that it is big enough to accomodate the index being set. 067 * <code>ArrayList</code> is the default indexed property that LazyDynaBean uses but 068 * this can be easily changed by overriding the <code>defaultIndexedProperty(name)</code> 069 * method.</p> 070 * 071 * <pre><code> 072 * DynaBean myBean = new LazyDynaBean(); 073 * myBean.set("myIndexedProperty", 0, "myValue1"); 074 * myBean.set("myIndexedProperty", 1, "myValue2"); 075 * </code></pre> 076 * 077 * <p>If the indexed property <b>does</b> exist in the <code>DynaClass</code> but is set to 078 * <code>null</code> in the <code>LazyDynaBean</code>, then it will instantiate a 079 * new <code>List</code> or <code>Array</code> as specified by the property's type 080 * in the <code>DynaClass</code> and automatically <i>grow</i> the <code>List</code> 081 * or <code>Array</code> so that it is big enough to accomodate the index being set.</p> 082 * 083 * <pre><code> 084 * DynaBean myBean = new LazyDynaBean(); 085 * MutableDynaClass myClass = (MutableDynaClass)myBean.getDynaClass(); 086 * myClass.add("myIndexedProperty", int[].class); 087 * myBean.set("myIndexedProperty", 0, new Integer(10)); 088 * myBean.set("myIndexedProperty", 1, new Integer(20)); 089 * </code></pre> 090 * 091 * <p><b><u>Setting Mapped Properties</u></b></p> 092 * <p>If the property <b>doesn't</b> exist, the <code>LazyDynaBean</code> will automatically add 093 * a property with a <code>HashMap</code> type to the <code>DynaClass</code> and 094 * instantiate a new <code>HashMap</code> in the DynaBean when the 095 * <code>set(name, key, value)</code> method is called. <code>HashMap</code> is the default 096 * mapped property that LazyDynaBean uses but this can be easily changed by overriding 097 * the <code>defaultMappedProperty(name)</code> method.</p> 098 * 099 * <pre><code> 100 * DynaBean myBean = new LazyDynaBean(); 101 * myBean.set("myMappedProperty", "myKey", "myValue"); 102 * </code></pre> 103 * 104 * <p>If the mapped property <b>does</b> exist in the <code>DynaClass</code> but is set to 105 * <code>null</code> in the <code>LazyDynaBean</code>, then it will instantiate a 106 * new <code>Map</code> as specified by the property's type in the <code>DynaClass</code>.</p> 107 * 108 * <pre><code> 109 * DynaBean myBean = new LazyDynaBean(); 110 * MutableDynaClass myClass = (MutableDynaClass)myBean.getDynaClass(); 111 * myClass.add("myMappedProperty", TreeMap.class); 112 * myBean.set("myMappedProperty", "myKey", "myValue"); 113 * </code></pre> 114 * 115 * <p><b><u><i>Restricted</i> DynaClass</u></b></p> 116 * <p><code>MutableDynaClass</code> have a facility to <i>restrict</i> the <code>DynaClass</code> 117 * so that its properties cannot be modified. If the <code>MutableDynaClass</code> is 118 * restricted then calling any of the <code>set()</code> methods for a property which 119 * doesn't exist will result in a <code>IllegalArgumentException</code> being thrown.</p> 120 * 121 * @version $Id$ 122 * @see LazyDynaClass 123 */ 124public class LazyDynaBean implements DynaBean, Serializable { 125 126 127 /** 128 * Commons Logging 129 */ 130 private transient Log logger = LogFactory.getLog(LazyDynaBean.class); 131 132 /** BigInteger Zero */ 133 protected static final BigInteger BigInteger_ZERO = new BigInteger("0"); 134 /** BigDecimal Zero */ 135 protected static final BigDecimal BigDecimal_ZERO = new BigDecimal("0"); 136 /** Character Space */ 137 protected static final Character Character_SPACE = new Character(' '); 138 /** Byte Zero */ 139 protected static final Byte Byte_ZERO = new Byte((byte)0); 140 /** Short Zero */ 141 protected static final Short Short_ZERO = new Short((short)0); 142 /** Integer Zero */ 143 protected static final Integer Integer_ZERO = new Integer(0); 144 /** Long Zero */ 145 protected static final Long Long_ZERO = new Long(0); 146 /** Float Zero */ 147 protected static final Float Float_ZERO = new Float((byte)0); 148 /** Double Zero */ 149 protected static final Double Double_ZERO = new Double((byte)0); 150 151 /** 152 * The <code>MutableDynaClass</code> "base class" that this DynaBean 153 * is associated with. 154 */ 155 protected Map<String, Object> values; 156 157 /** Map decorator for this DynaBean */ 158 private transient Map<String, Object> mapDecorator; 159 160 /** 161 * The <code>MutableDynaClass</code> "base class" that this DynaBean 162 * is associated with. 163 */ 164 protected MutableDynaClass dynaClass; 165 166 167 // ------------------- Constructors ---------------------------------- 168 169 /** 170 * Construct a new <code>LazyDynaBean</code> with a <code>LazyDynaClass</code> instance. 171 */ 172 public LazyDynaBean() { 173 this(new LazyDynaClass()); 174 } 175 176 /** 177 * Construct a new <code>LazyDynaBean</code> with a <code>LazyDynaClass</code> instance. 178 * 179 * @param name Name of this DynaBean class 180 */ 181 public LazyDynaBean(final String name) { 182 this(new LazyDynaClass(name)); 183 } 184 185 /** 186 * Construct a new <code>DynaBean</code> associated with the specified 187 * <code>DynaClass</code> instance - if its not a <code>MutableDynaClass</code> 188 * then a new <code>LazyDynaClass</code> is created and the properties copied. 189 * 190 * @param dynaClass The DynaClass we are associated with 191 */ 192 public LazyDynaBean(final DynaClass dynaClass) { 193 194 values = newMap(); 195 196 if (dynaClass instanceof MutableDynaClass) { 197 this.dynaClass = (MutableDynaClass)dynaClass; 198 } else { 199 this.dynaClass = new LazyDynaClass(dynaClass.getName(), dynaClass.getDynaProperties()); 200 } 201 202 } 203 204 205 // ------------------- Public Methods ---------------------------------- 206 207 /** 208 * <p> 209 * Return a Map representation of this DynaBean. 210 * </p> 211 * This, for example, could be used in JSTL in the following way to access 212 * a DynaBean's <code>fooProperty</code>: 213 * <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul> 214 * 215 * @return a Map representation of this DynaBean 216 */ 217 public Map<String, Object> getMap() { 218 // cache the Map 219 if (mapDecorator == null) { 220 mapDecorator = new DynaBeanPropertyMapDecorator(this); 221 } 222 return mapDecorator; 223 } 224 225 /** 226 * <p>Return the size of an indexed or mapped property.</p> 227 * 228 * @param name Name of the property 229 * @return The indexed or mapped property size 230 * @throws IllegalArgumentException if no property name is specified 231 */ 232 public int size(final String name) { 233 234 if (name == null) { 235 throw new IllegalArgumentException("No property name specified"); 236 } 237 238 final Object value = values.get(name); 239 if (value == null) { 240 return 0; 241 } 242 243 if (value instanceof Map) { 244 return ((Map<?, ?>)value).size(); 245 } 246 247 if (value instanceof List) { 248 return ((List<?>)value).size(); 249 } 250 251 if ((value.getClass().isArray())) { 252 return Array.getLength(value); 253 } 254 255 return 0; 256 257 } 258 259 // ------------------- DynaBean Methods ---------------------------------- 260 261 /** 262 * Does the specified mapped property contain a value for the specified 263 * key value? 264 * 265 * @param name Name of the property to check 266 * @param key Name of the key to check 267 * @return <code>true</code> if the mapped property contains a value for 268 * the specified key, otherwise <code>false</code> 269 * 270 * @throws IllegalArgumentException if no property name is specified 271 */ 272 public boolean contains(final String name, final String key) { 273 274 if (name == null) { 275 throw new IllegalArgumentException("No property name specified"); 276 } 277 278 final Object value = values.get(name); 279 if (value == null) { 280 return false; 281 } 282 283 if (value instanceof Map) { 284 return (((Map<?, ?>) value).containsKey(key)); 285 } 286 287 return false; 288 289 } 290 291 /** 292 * <p>Return the value of a simple property with the specified name.</p> 293 * 294 * <p><strong>N.B.</strong> Returns <code>null</code> if there is no property 295 * of the specified name.</p> 296 * 297 * @param name Name of the property whose value is to be retrieved. 298 * @return The property's value 299 * @throws IllegalArgumentException if no property name is specified 300 */ 301 public Object get(final String name) { 302 303 if (name == null) { 304 throw new IllegalArgumentException("No property name specified"); 305 } 306 307 // Value found 308 Object value = values.get(name); 309 if (value != null) { 310 return value; 311 } 312 313 // Property doesn't exist 314 if (!isDynaProperty(name)) { 315 return null; 316 } 317 318 // Property doesn't exist 319 value = createProperty(name, dynaClass.getDynaProperty(name).getType()); 320 321 if (value != null) { 322 set(name, value); 323 } 324 325 return value; 326 327 } 328 329 /** 330 * <p>Return the value of an indexed property with the specified name.</p> 331 * 332 * <p><strong>N.B.</strong> Returns <code>null</code> if there is no 'indexed' 333 * property of the specified name.</p> 334 * 335 * @param name Name of the property whose value is to be retrieved 336 * @param index Index of the value to be retrieved 337 * @return The indexed property's value 338 * 339 * @throws IllegalArgumentException if the specified property 340 * exists, but is not indexed 341 * @throws IndexOutOfBoundsException if the specified index 342 * is outside the range of the underlying property 343 */ 344 public Object get(final String name, final int index) { 345 346 // If its not a property, then create default indexed property 347 if (!isDynaProperty(name)) { 348 set(name, defaultIndexedProperty(name)); 349 } 350 351 // Get the indexed property 352 Object indexedProperty = get(name); 353 354 // Check that the property is indexed 355 if (!dynaClass.getDynaProperty(name).isIndexed()) { 356 throw new IllegalArgumentException 357 ("Non-indexed property for '" + name + "[" + index + "]' " 358 + dynaClass.getDynaProperty(name).getName()); 359 } 360 361 // Grow indexed property to appropriate size 362 indexedProperty = growIndexedProperty(name, indexedProperty, index); 363 364 // Return the indexed value 365 if (indexedProperty.getClass().isArray()) { 366 return Array.get(indexedProperty, index); 367 } else if (indexedProperty instanceof List) { 368 return ((List<?>)indexedProperty).get(index); 369 } else { 370 throw new IllegalArgumentException 371 ("Non-indexed property for '" + name + "[" + index + "]' " 372 + indexedProperty.getClass().getName()); 373 } 374 375 } 376 377 /** 378 * <p>Return the value of a mapped property with the specified name.</p> 379 * 380 * <p><strong>N.B.</strong> Returns <code>null</code> if there is no 'mapped' 381 * property of the specified name.</p> 382 * 383 * @param name Name of the property whose value is to be retrieved 384 * @param key Key of the value to be retrieved 385 * @return The mapped property's value 386 * 387 * @throws IllegalArgumentException if the specified property 388 * exists, but is not mapped 389 */ 390 public Object get(final String name, final String key) { 391 392 // If its not a property, then create default mapped property 393 if (!isDynaProperty(name)) { 394 set(name, defaultMappedProperty(name)); 395 } 396 397 // Get the mapped property 398 final Object mappedProperty = get(name); 399 400 // Check that the property is mapped 401 if (!dynaClass.getDynaProperty(name).isMapped()) { 402 throw new IllegalArgumentException 403 ("Non-mapped property for '" + name + "(" + key + ")' " 404 + dynaClass.getDynaProperty(name).getType().getName()); 405 } 406 407 // Get the value from the Map 408 if (mappedProperty instanceof Map) { 409 return (((Map<?, ?>) mappedProperty).get(key)); 410 } else { 411 throw new IllegalArgumentException 412 ("Non-mapped property for '" + name + "(" + key + ")'" 413 + mappedProperty.getClass().getName()); 414 } 415 416 } 417 418 419 /** 420 * Return the <code>DynaClass</code> instance that describes the set of 421 * properties available for this DynaBean. 422 * 423 * @return The associated DynaClass 424 */ 425 public DynaClass getDynaClass() { 426 return dynaClass; 427 } 428 429 /** 430 * Remove any existing value for the specified key on the 431 * specified mapped property. 432 * 433 * @param name Name of the property for which a value is to 434 * be removed 435 * @param key Key of the value to be removed 436 * 437 * @throws IllegalArgumentException if there is no property 438 * of the specified name 439 */ 440 public void remove(final String name, final String key) { 441 442 if (name == null) { 443 throw new IllegalArgumentException("No property name specified"); 444 } 445 446 final Object value = values.get(name); 447 if (value == null) { 448 return; 449 } 450 451 if (value instanceof Map) { 452 ((Map<?, ?>) value).remove(key); 453 } else { 454 throw new IllegalArgumentException 455 ("Non-mapped property for '" + name + "(" + key + ")'" 456 + value.getClass().getName()); 457 } 458 459 } 460 461 /** 462 * Set the value of a simple property with the specified name. 463 * 464 * @param name Name of the property whose value is to be set 465 * @param value Value to which this property is to be set 466 * 467 * @throws IllegalArgumentException if this is not an existing property 468 * name for our DynaClass and the MutableDynaClass is restricted 469 * @throws ConversionException if the specified value cannot be 470 * converted to the type required for this property 471 * @throws NullPointerException if an attempt is made to set a 472 * primitive property to null 473 */ 474 public void set(final String name, final Object value) { 475 476 // If the property doesn't exist, then add it 477 if (!isDynaProperty(name)) { 478 479 if (dynaClass.isRestricted()) { 480 throw new IllegalArgumentException 481 ("Invalid property name '" + name + "' (DynaClass is restricted)"); 482 } 483 if (value == null) { 484 dynaClass.add(name); 485 } else { 486 dynaClass.add(name, value.getClass()); 487 } 488 489 } 490 491 final DynaProperty descriptor = dynaClass.getDynaProperty(name); 492 493 if (value == null) { 494 if (descriptor.getType().isPrimitive()) { 495 throw new NullPointerException 496 ("Primitive value for '" + name + "'"); 497 } 498 } else if (!isAssignable(descriptor.getType(), value.getClass())) { 499 throw new ConversionException 500 ("Cannot assign value of type '" + 501 value.getClass().getName() + 502 "' to property '" + name + "' of type '" + 503 descriptor.getType().getName() + "'"); 504 } 505 506 // Set the property's value 507 values.put(name, value); 508 509 } 510 511 /** 512 * Set the value of an indexed property with the specified name. 513 * 514 * @param name Name of the property whose value is to be set 515 * @param index Index of the property to be set 516 * @param value Value to which this property is to be set 517 * 518 * @throws ConversionException if the specified value cannot be 519 * converted to the type required for this property 520 * @throws IllegalArgumentException if there is no property 521 * of the specified name 522 * @throws IllegalArgumentException if the specified property 523 * exists, but is not indexed 524 * @throws IndexOutOfBoundsException if the specified index 525 * is outside the range of the underlying property 526 */ 527 public void set(final String name, final int index, final Object value) { 528 529 // If its not a property, then create default indexed property 530 if (!isDynaProperty(name)) { 531 set(name, defaultIndexedProperty(name)); 532 } 533 534 // Get the indexed property 535 Object indexedProperty = get(name); 536 537 // Check that the property is indexed 538 if (!dynaClass.getDynaProperty(name).isIndexed()) { 539 throw new IllegalArgumentException 540 ("Non-indexed property for '" + name + "[" + index + "]'" 541 + dynaClass.getDynaProperty(name).getType().getName()); 542 } 543 544 // Grow indexed property to appropriate size 545 indexedProperty = growIndexedProperty(name, indexedProperty, index); 546 547 // Set the value in an array 548 if (indexedProperty.getClass().isArray()) { 549 Array.set(indexedProperty, index, value); 550 } else if (indexedProperty instanceof List) { 551 @SuppressWarnings("unchecked") 552 final 553 // Indexed properties are stored in a List<Object> 554 List<Object> values = (List<Object>) indexedProperty; 555 values.set(index, value); 556 } else { 557 throw new IllegalArgumentException 558 ("Non-indexed property for '" + name + "[" + index + "]' " 559 + indexedProperty.getClass().getName()); 560 } 561 562 } 563 564 /** 565 * Set the value of a mapped property with the specified name. 566 * 567 * @param name Name of the property whose value is to be set 568 * @param key Key of the property to be set 569 * @param value Value to which this property is to be set 570 * 571 * @throws ConversionException if the specified value cannot be 572 * converted to the type required for this property 573 * @throws IllegalArgumentException if there is no property 574 * of the specified name 575 * @throws IllegalArgumentException if the specified property 576 * exists, but is not mapped 577 */ 578 public void set(final String name, final String key, final Object value) { 579 580 // If the 'mapped' property doesn't exist, then add it 581 if (!isDynaProperty(name)) { 582 set(name, defaultMappedProperty(name)); 583 } 584 585 // Get the mapped property 586 final Object mappedProperty = get(name); 587 588 // Check that the property is mapped 589 if (!dynaClass.getDynaProperty(name).isMapped()) { 590 throw new IllegalArgumentException 591 ("Non-mapped property for '" + name + "(" + key + ")'" 592 + dynaClass.getDynaProperty(name).getType().getName()); 593 } 594 595 // Set the value in the Map 596 @SuppressWarnings("unchecked") 597 final 598 // mapped properties are stored in a Map<String, Object> 599 Map<String, Object> valuesMap = (Map<String, Object>) mappedProperty; 600 valuesMap.put(key, value); 601 602 } 603 604 // ------------------- protected Methods ---------------------------------- 605 606 /** 607 * Grow the size of an indexed property 608 * @param name The name of the property 609 * @param indexedProperty The current property value 610 * @param index The indexed value to grow the property to (i.e. one less than 611 * the required size) 612 * @return The new property value (grown to the appropriate size) 613 */ 614 protected Object growIndexedProperty(final String name, Object indexedProperty, final int index) { 615 616 // Grow a List to the appropriate size 617 if (indexedProperty instanceof List) { 618 619 @SuppressWarnings("unchecked") 620 final 621 // Indexed properties are stored as List<Object> 622 List<Object> list = (List<Object>)indexedProperty; 623 while (index >= list.size()) { 624 final Class<?> contentType = getDynaClass().getDynaProperty(name).getContentType(); 625 Object value = null; 626 if (contentType != null) { 627 value = createProperty(name+"["+list.size()+"]", contentType); 628 } 629 list.add(value); 630 } 631 632 } 633 634 // Grow an Array to the appropriate size 635 if ((indexedProperty.getClass().isArray())) { 636 637 final int length = Array.getLength(indexedProperty); 638 if (index >= length) { 639 final Class<?> componentType = indexedProperty.getClass().getComponentType(); 640 final Object newArray = Array.newInstance(componentType, (index + 1)); 641 System.arraycopy(indexedProperty, 0, newArray, 0, length); 642 indexedProperty = newArray; 643 set(name, indexedProperty); 644 final int newLength = Array.getLength(indexedProperty); 645 for (int i = length; i < newLength; i++) { 646 Array.set(indexedProperty, i, createProperty(name+"["+i+"]", componentType)); 647 } 648 } 649 } 650 651 return indexedProperty; 652 653 } 654 655 /** 656 * Create a new Instance of a Property 657 * @param name The name of the property 658 * @param type The class of the property 659 * @return The new value 660 */ 661 protected Object createProperty(final String name, final Class<?> type) { 662 if (type == null) { 663 return null; 664 } 665 666 // Create Lists, arrays or DynaBeans 667 if (type.isArray() || List.class.isAssignableFrom(type)) { 668 return createIndexedProperty(name, type); 669 } 670 671 if (Map.class.isAssignableFrom(type)) { 672 return createMappedProperty(name, type); 673 } 674 675 if (DynaBean.class.isAssignableFrom(type)) { 676 return createDynaBeanProperty(name, type); 677 } 678 679 if (type.isPrimitive()) { 680 return createPrimitiveProperty(name, type); 681 } 682 683 if (Number.class.isAssignableFrom(type)) { 684 return createNumberProperty(name, type); 685 } 686 687 return createOtherProperty(name, type); 688 689 } 690 691 /** 692 * Create a new Instance of an 'Indexed' Property 693 * @param name The name of the property 694 * @param type The class of the property 695 * @return The new value 696 */ 697 protected Object createIndexedProperty(final String name, final Class<?> type) { 698 699 // Create the indexed object 700 Object indexedProperty = null; 701 702 if (type == null) { 703 704 indexedProperty = defaultIndexedProperty(name); 705 706 } else if (type.isArray()) { 707 708 indexedProperty = Array.newInstance(type.getComponentType(), 0); 709 710 } else if (List.class.isAssignableFrom(type)) { 711 if (type.isInterface()) { 712 indexedProperty = defaultIndexedProperty(name); 713 } else { 714 try { 715 indexedProperty = type.newInstance(); 716 } 717 catch (final Exception ex) { 718 throw new IllegalArgumentException 719 ("Error instantiating indexed property of type '" + 720 type.getName() + "' for '" + name + "' " + ex); 721 } 722 } 723 } else { 724 725 throw new IllegalArgumentException 726 ("Non-indexed property of type '" + type.getName() + "' for '" + name + "'"); 727 } 728 729 return indexedProperty; 730 731 } 732 733 /** 734 * Create a new Instance of a 'Mapped' Property 735 * @param name The name of the property 736 * @param type The class of the property 737 * @return The new value 738 */ 739 protected Object createMappedProperty(final String name, final Class<?> type) { 740 741 // Create the mapped object 742 Object mappedProperty = null; 743 744 if (type == null) { 745 746 mappedProperty = defaultMappedProperty(name); 747 748 } else if (type.isInterface()) { 749 750 mappedProperty = defaultMappedProperty(name); 751 752 } else if (Map.class.isAssignableFrom(type)) { 753 try { 754 mappedProperty = type.newInstance(); 755 } 756 catch (final Exception ex) { 757 throw new IllegalArgumentException 758 ("Error instantiating mapped property of type '" + 759 type.getName() + "' for '" + name + "' " + ex); 760 } 761 } else { 762 763 throw new IllegalArgumentException 764 ("Non-mapped property of type '" + type.getName() + "' for '" + name + "'"); 765 } 766 767 return mappedProperty; 768 769 } 770 771 /** 772 * Create a new Instance of a 'DynaBean' Property. 773 * @param name The name of the property 774 * @param type The class of the property 775 * @return The new value 776 */ 777 protected Object createDynaBeanProperty(final String name, final Class<?> type) { 778 try { 779 return type.newInstance(); 780 } 781 catch (final Exception ex) { 782 if (logger().isWarnEnabled()) { 783 logger().warn("Error instantiating DynaBean property of type '" + 784 type.getName() + "' for '" + name + "' " + ex); 785 } 786 return null; 787 } 788 } 789 790 /** 791 * Create a new Instance of a 'Primitive' Property. 792 * @param name The name of the property 793 * @param type The class of the property 794 * @return The new value 795 */ 796 protected Object createPrimitiveProperty(final String name, final Class<?> type) { 797 798 if (type == Boolean.TYPE) { 799 return Boolean.FALSE; 800 } else if (type == Integer.TYPE) { 801 return Integer_ZERO; 802 } else if (type == Long.TYPE) { 803 return Long_ZERO; 804 } else if (type == Double.TYPE) { 805 return Double_ZERO; 806 } else if (type == Float.TYPE) { 807 return Float_ZERO; 808 } else if (type == Byte.TYPE) { 809 return Byte_ZERO; 810 } else if (type == Short.TYPE) { 811 return Short_ZERO; 812 } else if (type == Character.TYPE) { 813 return Character_SPACE; 814 } else { 815 return null; 816 } 817 818 } 819 820 /** 821 * Create a new Instance of a <code>java.lang.Number</code> Property. 822 * @param name The name of the property 823 * @param type The class of the property 824 * @return The new value 825 */ 826 protected Object createNumberProperty(final String name, final Class<?> type) { 827 828 return null; 829 830 } 831 832 /** 833 * Create a new Instance of other Property types 834 * @param name The name of the property 835 * @param type The class of the property 836 * @return The new value 837 */ 838 protected Object createOtherProperty(final String name, final Class<?> type) { 839 840 if (type == Object.class || 841 type == String.class || 842 type == Boolean.class || 843 type == Character.class || 844 Date.class.isAssignableFrom(type)) { 845 846 return null; 847 848 } 849 850 try { 851 return type.newInstance(); 852 } 853 catch (final Exception ex) { 854 if (logger().isWarnEnabled()) { 855 logger().warn("Error instantiating property of type '" + type.getName() + "' for '" + name + "' " + ex); 856 } 857 return null; 858 } 859 } 860 861 /** 862 * <p>Creates a new <code>ArrayList</code> for an 'indexed' property 863 * which doesn't exist.</p> 864 * 865 * <p>This method should be overridden if an alternative <code>List</code> 866 * or <code>Array</code> implementation is required for 'indexed' properties.</p> 867 * 868 * @param name Name of the 'indexed property. 869 * @return The default value for an indexed property (java.util.ArrayList) 870 */ 871 protected Object defaultIndexedProperty(final String name) { 872 return new ArrayList<Object>(); 873 } 874 875 /** 876 * <p>Creates a new <code>HashMap</code> for a 'mapped' property 877 * which doesn't exist.</p> 878 * 879 * <p>This method can be overridden if an alternative <code>Map</code> 880 * implementation is required for 'mapped' properties.</p> 881 * 882 * @param name Name of the 'mapped property. 883 * @return The default value for a mapped property (java.util.HashMap) 884 */ 885 protected Map<String, Object> defaultMappedProperty(final String name) { 886 return new HashMap<String, Object>(); 887 } 888 889 /** 890 * Indicates if there is a property with the specified name. 891 * @param name The name of the property to check 892 * @return <code>true</code> if there is a property of the 893 * specified name, otherwise <code>false</code> 894 */ 895 protected boolean isDynaProperty(final String name) { 896 897 if (name == null) { 898 throw new IllegalArgumentException("No property name specified"); 899 } 900 901 // Handle LazyDynaClasses 902 if (dynaClass instanceof LazyDynaClass) { 903 return ((LazyDynaClass)dynaClass).isDynaProperty(name); 904 } 905 906 // Handle other MutableDynaClass 907 return dynaClass.getDynaProperty(name) == null ? false : true; 908 909 } 910 911 /** 912 * Is an object of the source class assignable to the destination class? 913 * 914 * @param dest Destination class 915 * @param source Source class 916 * @return <code>true</code> if the source class is assignable to the 917 * destination class, otherwise <code>false</code> 918 */ 919 protected boolean isAssignable(final Class<?> dest, final Class<?> source) { 920 921 if (dest.isAssignableFrom(source) || 922 ((dest == Boolean.TYPE) && (source == Boolean.class)) || 923 ((dest == Byte.TYPE) && (source == Byte.class)) || 924 ((dest == Character.TYPE) && (source == Character.class)) || 925 ((dest == Double.TYPE) && (source == Double.class)) || 926 ((dest == Float.TYPE) && (source == Float.class)) || 927 ((dest == Integer.TYPE) && (source == Integer.class)) || 928 ((dest == Long.TYPE) && (source == Long.class)) || 929 ((dest == Short.TYPE) && (source == Short.class))) { 930 return (true); 931 } else { 932 return (false); 933 } 934 935 } 936 937 /** 938 * <p>Creates a new instance of the <code>Map</code>.</p> 939 * @return a new Map instance 940 */ 941 protected Map<String, Object> newMap() { 942 return new HashMap<String, Object>(); 943 } 944 945 /** 946 * <p>Returns the <code>Log</code>. 947 */ 948 private Log logger() { 949 if (logger == null) { 950 logger = LogFactory.getLog(LazyDynaBean.class); 951 } 952 return logger; 953 } 954 955}