    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     */
    017    package org.apache.commons.beanutils;
    018
    019    import java.util.List;
    020    import java.util.ArrayList;
    021    import java.util.Map;
    022    import java.util.HashMap;
    023    import java.util.Date;
    024    import java.lang.reflect.Array;
    025    import java.math.BigDecimal;
    026    import java.math.BigInteger;
    027    import java.io.Serializable;
    028    import org.apache.commons.logging.Log;
    029    import 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     */
    113    public 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    }