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}