    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
    018
    019    package org.apache.commons.beanutils;
    020
    021
    022    import java.io.Serializable;
    023    import java.lang.reflect.Array;
    024    import java.util.HashMap;
    025    import java.util.List;
    026    import java.util.Map;
    027
    028
    029    /**
    030     * <p>Minimal implementation of the <code>DynaBean</code> interface.  Can be
    031     * used as a convenience base class for more sophisticated implementations.</p>
    032     *
    033     * <p><strong>IMPLEMENTATION NOTE</strong> - Instances of this class that are
    034     * accessed from multiple threads simultaneously need to be synchronized.</p>
    035     *
    036     * <p><strong>IMPLEMENTATION NOTE</strong> - Instances of this class can be
    037     * successfully serialized and deserialized <strong>ONLY</strong> if all
    038     * property values are <code>Serializable</code>.</p>
    039     *
    040     * @author Craig McClanahan
    041     * @version $Revision: 690380 $ $Date: 2008-08-29 21:04:38 +0100 (Fri, 29 Aug 2008) $
    042     */
    043
    044    public class BasicDynaBean implements DynaBean, Serializable {
    045
    046
    047        // ---------------------------------------------------------- Constructors
    048
    049
    050        /**
    051         * Construct a new <code>DynaBean</code> associated with the specified
    052         * <code>DynaClass</code> instance.
    053         *
    054         * @param dynaClass The DynaClass we are associated with
    055         */
    056        public BasicDynaBean(DynaClass dynaClass) {
    057
    058            super();
    059            this.dynaClass = dynaClass;
    060
    061        }
    062
    063
    064        // ---------------------------------------------------- Instance Variables
    065
    066
    067        /**
    068         * The <code>DynaClass</code> "base class" that this DynaBean
    069         * is associated with.
    070         */
    071        protected DynaClass dynaClass = null;
    072
    073
    074        /**
    075         * The set of property values for this DynaBean, keyed by property name.
    076         */
    077        protected HashMap values = new HashMap();
    078
    079        /** Map decorator for this DynaBean */
    080        private transient Map mapDecorator;
    081
    082        /**
    083         * Return a Map representation of this DynaBean.
    084         * </p>
    085         * This, for example, could be used in JSTL in the following way to access
    086         * a DynaBean's <code>fooProperty</code>:
    087         * <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul>
    088         *
    089         * @return a Map representation of this DynaBean
    090         * @since 1.8.0
    091         */
    092        public Map getMap() {
    093
    094            // cache the Map
    095            if (mapDecorator == null) {
    096                mapDecorator = new DynaBeanMapDecorator(this);
    097            }
    098            return mapDecorator;
    099
    100        }
    101
    102        // ------------------------------------------------------ DynaBean Methods
    103
    104
    105        /**
    106         * Does the specified mapped property contain a value for the specified
    107         * key value?
    108         *
    109         * @param name Name of the property to check
    110         * @param key Name of the key to check
    111         * @return <code>true<code> if the mapped property contains a value for
    112         * the specified key, otherwise <code>false</code>
    113         *
    114         * @exception IllegalArgumentException if there is no property
    115         *  of the specified name
    116         */
    117        public boolean contains(String name, String key) {
    118
    119            Object value = values.get(name);
    120            if (value == null) {
    121                throw new NullPointerException
    122                        ("No mapped value for '" + name + "(" + key + ")'");
    123            } else if (value instanceof Map) {
    124                return (((Map) value).containsKey(key));
    125            } else {
    126                throw new IllegalArgumentException
    127                        ("Non-mapped property for '" + name + "(" + key + ")'");
    128            }
    129
    130        }
    131
    132
    133        /**
    134         * Return the value of a simple property with the specified name.
    135         *
    136         * @param name Name of the property whose value is to be retrieved
    137         * @return The property's value
    138         *
    139         * @exception IllegalArgumentException if there is no property
    140         *  of the specified name
    141         */
    142        public Object get(String name) {
    143
    144            // Return any non-null value for the specified property
    145            Object value = values.get(name);
    146            if (value != null) {
    147                return (value);
    148            }
    149
    150            // Return a null value for a non-primitive property
    151            Class type = getDynaProperty(name).getType();
    152            if (!type.isPrimitive()) {
    153                return (value);
    154            }
    155
    156            // Manufacture default values for primitive properties
    157            if (type == Boolean.TYPE) {
    158                return (Boolean.FALSE);
    159            } else if (type == Byte.TYPE) {
    160                return (new Byte((byte) 0));
    161            } else if (type == Character.TYPE) {
    162                return (new Character((char) 0));
    163            } else if (type == Double.TYPE) {
    164                return (new Double(0.0));
    165            } else if (type == Float.TYPE) {
    166                return (new Float((float) 0.0));
    167            } else if (type == Integer.TYPE) {
    168                return (new Integer(0));
    169            } else if (type == Long.TYPE) {
    170                return (new Long(0));
    171            } else if (type == Short.TYPE) {
    172                return (new Short((short) 0));
    173            } else {
    174                return (null);
    175            }
    176
    177        }
    178
    179
    180        /**
    181         * Return the value of an indexed property with the specified name.
    182         *
    183         * @param name Name of the property whose value is to be retrieved
    184         * @param index Index of the value to be retrieved
    185         * @return The indexed property's value
    186         *
    187         * @exception IllegalArgumentException if there is no property
    188         *  of the specified name
    189         * @exception IllegalArgumentException if the specified property
    190         *  exists, but is not indexed
    191         * @exception IndexOutOfBoundsException if the specified index
    192         *  is outside the range of the underlying property
    193         * @exception NullPointerException if no array or List has been
    194         *  initialized for this property
    195         */
    196        public Object get(String name, int index) {
    197
    198            Object value = values.get(name);
    199            if (value == null) {
    200                throw new NullPointerException
    201                        ("No indexed value for '" + name + "[" + index + "]'");
    202            } else if (value.getClass().isArray()) {
    203                return (Array.get(value, index));
    204            } else if (value instanceof List) {
    205                return ((List) value).get(index);
    206            } else {
    207                throw new IllegalArgumentException
    208                        ("Non-indexed property for '" + name + "[" + index + "]'");
    209            }
    210
    211        }
    212
    213
    214        /**
    215         * Return the value of a mapped property with the specified name,
    216         * or <code>null</code> if there is no value for the specified key.
    217         *
    218         * @param name Name of the property whose value is to be retrieved
    219         * @param key Key of the value to be retrieved
    220         * @return The mapped property's value
    221         *
    222         * @exception IllegalArgumentException if there is no property
    223         *  of the specified name
    224         * @exception IllegalArgumentException if the specified property
    225         *  exists, but is not mapped
    226         */
    227        public Object get(String name, String key) {
    228
    229            Object value = values.get(name);
    230            if (value == null) {
    231                throw new NullPointerException
    232                        ("No mapped value for '" + name + "(" + key + ")'");
    233            } else if (value instanceof Map) {
    234                return (((Map) value).get(key));
    235            } else {
    236                throw new IllegalArgumentException
    237                        ("Non-mapped property for '" + name + "(" + key + ")'");
    238            }
    239
    240        }
    241
    242
    243        /**
    244         * Return the <code>DynaClass</code> instance that describes the set of
    245         * properties available for this DynaBean.
    246         *
    247         * @return The associated DynaClass
    248         */
    249        public DynaClass getDynaClass() {
    250
    251            return (this.dynaClass);
    252
    253        }
    254
    255
    256        /**
    257         * Remove any existing value for the specified key on the
    258         * specified mapped property.
    259         *
    260         * @param name Name of the property for which a value is to
    261         *  be removed
    262         * @param key Key of the value to be removed
    263         *
    264         * @exception IllegalArgumentException if there is no property
    265         *  of the specified name
    266         */
    267        public void remove(String name, String key) {
    268
    269            Object value = values.get(name);
    270            if (value == null) {
    271                throw new NullPointerException
    272                        ("No mapped value for '" + name + "(" + key + ")'");
    273            } else if (value instanceof Map) {
    274                ((Map) value).remove(key);
    275            } else {
    276                throw new IllegalArgumentException
    277                        ("Non-mapped property for '" + name + "(" + key + ")'");
    278            }
    279
    280        }
    281
    282
    283        /**
    284         * Set the value of a simple property with the specified name.
    285         *
    286         * @param name Name of the property whose value is to be set
    287         * @param value Value to which this property is to be set
    288         *
    289         * @exception ConversionException if the specified value cannot be
    290         *  converted to the type required for this property
    291         * @exception IllegalArgumentException if there is no property
    292         *  of the specified name
    293         * @exception NullPointerException if an attempt is made to set a
    294         *  primitive property to null
    295         */
    296        public void set(String name, Object value) {
    297
    298            DynaProperty descriptor = getDynaProperty(name);
    299            if (value == null) {
    300                if (descriptor.getType().isPrimitive()) {
    301                    throw new NullPointerException
    302                            ("Primitive value for '" + name + "'");
    303                }
    304            } else if (!isAssignable(descriptor.getType(), value.getClass())) {
    305                throw new ConversionException
    306                        ("Cannot assign value of type '" +
    307                        value.getClass().getName() +
    308                        "' to property '" + name + "' of type '" +
    309                        descriptor.getType().getName() + "'");
    310            }
    311            values.put(name, value);
    312
    313        }
    314
    315
    316        /**
    317         * Set the value of an indexed property with the specified name.
    318         *
    319         * @param name Name of the property whose value is to be set
    320         * @param index Index of the property to be set
    321         * @param value Value to which this property is to be set
    322         *
    323         * @exception ConversionException if the specified value cannot be
    324         *  converted to the type required for this property
    325         * @exception IllegalArgumentException if there is no property
    326         *  of the specified name
    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 void set(String name, int index, Object value) {
    333
    334            Object prop = values.get(name);
    335            if (prop == null) {
    336                throw new NullPointerException
    337                        ("No indexed value for '" + name + "[" + index + "]'");
    338            } else if (prop.getClass().isArray()) {
    339                Array.set(prop, index, value);
    340            } else if (prop instanceof List) {
    341                try {
    342                    ((List) prop).set(index, value);
    343                } catch (ClassCastException e) {
    344                    throw new ConversionException(e.getMessage());
    345                }
    346            } else {
    347                throw new IllegalArgumentException
    348                        ("Non-indexed property for '" + name + "[" + index + "]'");
    349            }
    350
    351        }
    352
    353
    354        /**
    355         * Set the value of a mapped property with the specified name.
    356         *
    357         * @param name Name of the property whose value is to be set
    358         * @param key Key of the property to be set
    359         * @param value Value to which this property is to be set
    360         *
    361         * @exception ConversionException if the specified value cannot be
    362         *  converted to the type required for this property
    363         * @exception IllegalArgumentException if there is no property
    364         *  of the specified name
    365         * @exception IllegalArgumentException if the specified property
    366         *  exists, but is not mapped
    367         */
    368        public void set(String name, String key, Object value) {
    369
    370            Object prop = values.get(name);
    371            if (prop == null) {
    372                throw new NullPointerException
    373                        ("No mapped value for '" + name + "(" + key + ")'");
    374            } else if (prop instanceof Map) {
    375                ((Map) prop).put(key, value);
    376            } else {
    377                throw new IllegalArgumentException
    378                        ("Non-mapped property for '" + name + "(" + key + ")'");
    379            }
    380
    381        }
    382
    383
    384        // ------------------------------------------------------ Protected Methods
    385
    386
    387        /**
    388         * Return the property descriptor for the specified property name.
    389         *
    390         * @param name Name of the property for which to retrieve the descriptor
    391         * @return The property descriptor
    392         *
    393         * @exception IllegalArgumentException if this is not a valid property
    394         *  name for our DynaClass
    395         */
    396        protected DynaProperty getDynaProperty(String name) {
    397
    398            DynaProperty descriptor = getDynaClass().getDynaProperty(name);
    399            if (descriptor == null) {
    400                throw new IllegalArgumentException
    401                        ("Invalid property name '" + name + "'");
    402            }
    403            return (descriptor);
    404
    405        }
    406
    407
    408        /**
    409         * Is an object of the source class assignable to the destination class?
    410         *
    411         * @param dest Destination class
    412         * @param source Source class
    413         * @return <code>true</code> if the source class is assignable to the
    414         * destination class, otherwise <code>false</code>
    415         */
    416        protected boolean isAssignable(Class dest, Class source) {
    417
    418            if (dest.isAssignableFrom(source) ||
    419                    ((dest == Boolean.TYPE) && (source == Boolean.class)) ||
    420                    ((dest == Byte.TYPE) && (source == Byte.class)) ||
    421                    ((dest == Character.TYPE) && (source == Character.class)) ||
    422                    ((dest == Double.TYPE) && (source == Double.class)) ||
    423                    ((dest == Float.TYPE) && (source == Float.class)) ||
    424                    ((dest == Integer.TYPE) && (source == Integer.class)) ||
    425                    ((dest == Long.TYPE) && (source == Long.class)) ||
    426                    ((dest == Short.TYPE) && (source == Short.class))) {
    427                return (true);
    428            } else {
    429                return (false);
    430            }
    431
    432        }
    433
    434
    435    }