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