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