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