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}