View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18
    19  package org.apache.commons.beanutils;
    20
    21
    22  import java.io.Serializable;
    23  import java.lang.reflect.Array;
    24  import java.util.HashMap;
    25  import java.util.List;
    26  import java.util.Map;
    27
    28
    29  /***
    30   * <p>Minimal implementation of the <code>DynaBean</code> interface.  Can be
    31   * used as a convenience base class for more sophisticated implementations.</p>
    32   *
    33   * <p><strong>IMPLEMENTATION NOTE</strong> - Instances of this class that are
    34   * accessed from multiple threads simultaneously need to be synchronized.</p>
    35   *
    36   * <p><strong>IMPLEMENTATION NOTE</strong> - Instances of this class can be
    37   * successfully serialized and deserialized <strong>ONLY</strong> if all
    38   * property values are <code>Serializable</code>.</p>
    39   *
    40   * @author Craig McClanahan
    41   * @version $Revision: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $
    42   */
    43
    44  public class BasicDynaBean implements DynaBean, Serializable {
    45
    46
    47      // ---------------------------------------------------------- Constructors
    48
    49
    50      /***
    51       * Construct a new <code>DynaBean</code> associated with the specified
    52       * <code>DynaClass</code> instance.
    53       *
    54       * @param dynaClass The DynaClass we are associated with
    55       */
    56      public BasicDynaBean(DynaClass dynaClass) {
    57
    58          super();
    59          this.dynaClass = dynaClass;
    60
    61      }
    62
    63
    64      // ---------------------------------------------------- Instance Variables
    65
    66
    67      /***
    68       * The <code>DynaClass</code> "base class" that this DynaBean
    69       * is associated with.
    70       */
    71      protected DynaClass dynaClass = null;
    72
    73
    74      /***
    75       * The set of property values for this DynaBean, keyed by property name.
    76       */
    77      protected HashMap values = new HashMap();
    78
    79      /*** Map decorator for this DynaBean */
    80      private transient Map mapDecorator;
    81
    82      /***
    83       * Return a Map representation of this DynaBean.
    84       * </p>
    85       * This, for example, could be used in JSTL in the following way to access
    86       * a DynaBean's <code>fooProperty</code>:
    87       * <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul>
    88       *
    89       * @return a Map representation of this DynaBean
    90       */
    91      public Map getMap() {
    92
    93          // cache the Map
    94          if (mapDecorator == null) {
    95              mapDecorator = new DynaBeanMapDecorator(this);
    96          }
    97          return mapDecorator;
    98
    99      }
    100
    101     // ------------------------------------------------------ DynaBean Methods
    102
    103
    104     /***
    105      * Does the specified mapped property contain a value for the specified
    106      * key value?
    107      *
    108      * @param name Name of the property to check
    109      * @param key Name of the key to check
    110      * @return <code>true<code> if the mapped property contains a value for
    111      * the specified key, otherwise <code>false</code>
    112      *
    113      * @exception IllegalArgumentException if there is no property
    114      *  of the specified name
    115      */
    116     public boolean contains(String name, String key) {
    117
    118         Object value = values.get(name);
    119         if (value == null) {
    120             throw new NullPointerException
    121                     ("No mapped value for '" + name + "(" + key + ")'");
    122         } else if (value instanceof Map) {
    123             return (((Map) value).containsKey(key));
    124         } else {
    125             throw new IllegalArgumentException
    126                     ("Non-mapped property for '" + name + "(" + key + ")'");
    127         }
    128
    129     }
    130
    131
    132     /***
    133      * Return the value of a simple property with the specified name.
    134      *
    135      * @param name Name of the property whose value is to be retrieved
    136      * @return The property's value
    137      *
    138      * @exception IllegalArgumentException if there is no property
    139      *  of the specified name
    140      */
    141     public Object get(String name) {
    142
    143         // Return any non-null value for the specified property
    144         Object value = values.get(name);
    145         if (value != null) {
    146             return (value);
    147         }
    148
    149         // Return a null value for a non-primitive property
    150         Class type = getDynaProperty(name).getType();
    151         if (!type.isPrimitive()) {
    152             return (value);
    153         }
    154
    155         // Manufacture default values for primitive properties
    156         if (type == Boolean.TYPE) {
    157             return (Boolean.FALSE);
    158         } else if (type == Byte.TYPE) {
    159             return (new Byte((byte) 0));
    160         } else if (type == Character.TYPE) {
    161             return (new Character((char) 0));
    162         } else if (type == Double.TYPE) {
    163             return (new Double(0.0));
    164         } else if (type == Float.TYPE) {
    165             return (new Float((float) 0.0));
    166         } else if (type == Integer.TYPE) {
    167             return (new Integer(0));
    168         } else if (type == Long.TYPE) {
    169             return (new Long(0));
    170         } else if (type == Short.TYPE) {
    171             return (new Short((short) 0));
    172         } else {
    173             return (null);
    174         }
    175
    176     }
    177
    178
    179     /***
    180      * Return the value of an indexed property with the specified name.
    181      *
    182      * @param name Name of the property whose value is to be retrieved
    183      * @param index Index of the value to be retrieved
    184      * @return The indexed property's value
    185      *
    186      * @exception IllegalArgumentException if there is no property
    187      *  of the specified name
    188      * @exception IllegalArgumentException if the specified property
    189      *  exists, but is not indexed
    190      * @exception IndexOutOfBoundsException if the specified index
    191      *  is outside the range of the underlying property
    192      * @exception NullPointerException if no array or List has been
    193      *  initialized for this property
    194      */
    195     public Object get(String name, int index) {
    196
    197         Object value = values.get(name);
    198         if (value == null) {
    199             throw new NullPointerException
    200                     ("No indexed value for '" + name + "[" + index + "]'");
    201         } else if (value.getClass().isArray()) {
    202             return (Array.get(value, index));
    203         } else if (value instanceof List) {
    204             return ((List) value).get(index);
    205         } else {
    206             throw new IllegalArgumentException
    207                     ("Non-indexed property for '" + name + "[" + index + "]'");
    208         }
    209
    210     }
    211
    212
    213     /***
    214      * Return the value of a mapped property with the specified name,
    215      * or <code>null</code> if there is no value for the specified key.
    216      *
    217      * @param name Name of the property whose value is to be retrieved
    218      * @param key Key of the value to be retrieved
    219      * @return The mapped property's value
    220      *
    221      * @exception IllegalArgumentException if there is no property
    222      *  of the specified name
    223      * @exception IllegalArgumentException if the specified property
    224      *  exists, but is not mapped
    225      */
    226     public Object get(String name, String key) {
    227
    228         Object value = values.get(name);
    229         if (value == null) {
    230             throw new NullPointerException
    231                     ("No mapped value for '" + name + "(" + key + ")'");
    232         } else if (value instanceof Map) {
    233             return (((Map) value).get(key));
    234         } else {
    235             throw new IllegalArgumentException
    236                     ("Non-mapped property for '" + name + "(" + key + ")'");
    237         }
    238
    239     }
    240
    241
    242     /***
    243      * Return the <code>DynaClass</code> instance that describes the set of
    244      * properties available for this DynaBean.
    245      *
    246      * @return The associated DynaClass
    247      */
    248     public DynaClass getDynaClass() {
    249
    250         return (this.dynaClass);
    251
    252     }
    253
    254
    255     /***
    256      * Remove any existing value for the specified key on the
    257      * specified mapped property.
    258      *
    259      * @param name Name of the property for which a value is to
    260      *  be removed
    261      * @param key Key of the value to be removed
    262      *
    263      * @exception IllegalArgumentException if there is no property
    264      *  of the specified name
    265      */
    266     public void remove(String name, String key) {
    267
    268         Object value = values.get(name);
    269         if (value == null) {
    270             throw new NullPointerException
    271                     ("No mapped value for '" + name + "(" + key + ")'");
    272         } else if (value instanceof Map) {
    273             ((Map) value).remove(key);
    274         } else {
    275             throw new IllegalArgumentException
    276                     ("Non-mapped property for '" + name + "(" + key + ")'");
    277         }
    278
    279     }
    280
    281
    282     /***
    283      * Set the value of a simple property with the specified name.
    284      *
    285      * @param name Name of the property whose value is to be set
    286      * @param value Value to which this property is to be set
    287      *
    288      * @exception ConversionException if the specified value cannot be
    289      *  converted to the type required for this property
    290      * @exception IllegalArgumentException if there is no property
    291      *  of the specified name
    292      * @exception NullPointerException if an attempt is made to set a
    293      *  primitive property to null
    294      */
    295     public void set(String name, Object value) {
    296
    297         DynaProperty descriptor = getDynaProperty(name);
    298         if (value == null) {
    299             if (descriptor.getType().isPrimitive()) {
    300                 throw new NullPointerException
    301                         ("Primitive value for '" + name + "'");
    302             }
    303         } else if (!isAssignable(descriptor.getType(), value.getClass())) {
    304             throw new ConversionException
    305                     ("Cannot assign value of type '" +
    306                     value.getClass().getName() +
    307                     "' to property '" + name + "' of type '" +
    308                     descriptor.getType().getName() + "'");
    309         }
    310         values.put(name, value);
    311
    312     }
    313
    314
    315     /***
    316      * Set the value of an indexed property with the specified name.
    317      *
    318      * @param name Name of the property whose value is to be set
    319      * @param index Index of the property to be set
    320      * @param value Value to which this property is to be set
    321      *
    322      * @exception ConversionException if the specified value cannot be
    323      *  converted to the type required for this property
    324      * @exception IllegalArgumentException if there is no property
    325      *  of the specified name
    326      * @exception IllegalArgumentException if the specified property
    327      *  exists, but is not indexed
    328      * @exception IndexOutOfBoundsException if the specified index
    329      *  is outside the range of the underlying property
    330      */
    331     public void set(String name, int index, Object value) {
    332
    333         Object prop = values.get(name);
    334         if (prop == null) {
    335             throw new NullPointerException
    336                     ("No indexed value for '" + name + "[" + index + "]'");
    337         } else if (prop.getClass().isArray()) {
    338             Array.set(prop, index, value);
    339         } else if (prop instanceof List) {
    340             try {
    341                 ((List) prop).set(index, value);
    342             } catch (ClassCastException e) {
    343                 throw new ConversionException(e.getMessage());
    344             }
    345         } else {
    346             throw new IllegalArgumentException
    347                     ("Non-indexed property for '" + name + "[" + index + "]'");
    348         }
    349
    350     }
    351
    352
    353     /***
    354      * Set the value of a mapped property with the specified name.
    355      *
    356      * @param name Name of the property whose value is to be set
    357      * @param key Key of the property to be set
    358      * @param value Value to which this property is to be set
    359      *
    360      * @exception ConversionException if the specified value cannot be
    361      *  converted to the type required for this property
    362      * @exception IllegalArgumentException if there is no property
    363      *  of the specified name
    364      * @exception IllegalArgumentException if the specified property
    365      *  exists, but is not mapped
    366      */
    367     public void set(String name, String key, Object value) {
    368
    369         Object prop = values.get(name);
    370         if (prop == null) {
    371             throw new NullPointerException
    372                     ("No mapped value for '" + name + "(" + key + ")'");
    373         } else if (prop instanceof Map) {
    374             ((Map) prop).put(key, value);
    375         } else {
    376             throw new IllegalArgumentException
    377                     ("Non-mapped property for '" + name + "(" + key + ")'");
    378         }
    379
    380     }
    381
    382
    383     // ------------------------------------------------------ Protected Methods
    384
    385
    386     /***
    387      * Return the property descriptor for the specified property name.
    388      *
    389      * @param name Name of the property for which to retrieve the descriptor
    390      * @return The property descriptor
    391      *
    392      * @exception IllegalArgumentException if this is not a valid property
    393      *  name for our DynaClass
    394      */
    395     protected DynaProperty getDynaProperty(String name) {
    396
    397         DynaProperty descriptor = getDynaClass().getDynaProperty(name);
    398         if (descriptor == null) {
    399             throw new IllegalArgumentException
    400                     ("Invalid property name '" + name + "'");
    401         }
    402         return (descriptor);
    403
    404     }
    405
    406
    407     /***
    408      * Is an object of the source class assignable to the destination class?
    409      *
    410      * @param dest Destination class
    411      * @param source Source class
    412      * @return <code>true</code> if the source class is assignable to the
    413      * destination class, otherwise <code>false</code>
    414      */
    415     protected boolean isAssignable(Class dest, Class source) {
    416
    417         if (dest.isAssignableFrom(source) ||
    418                 ((dest == Boolean.TYPE) && (source == Boolean.class)) ||
    419                 ((dest == Byte.TYPE) && (source == Byte.class)) ||
    420                 ((dest == Character.TYPE) && (source == Character.class)) ||
    421                 ((dest == Double.TYPE) && (source == Double.class)) ||
    422                 ((dest == Float.TYPE) && (source == Float.class)) ||
    423                 ((dest == Integer.TYPE) && (source == Integer.class)) ||
    424                 ((dest == Long.TYPE) && (source == Long.class)) ||
    425                 ((dest == Short.TYPE) && (source == Short.class))) {
    426             return (true);
    427         } else {
    428             return (false);
    429         }
    430
    431     }
    432
    433
    434 }