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  package org.apache.commons.beanutils;
    18
    19  import java.util.Map;
    20  import java.util.List;
    21  import java.util.ArrayList;
    22  import java.util.Set;
    23  import java.util.HashSet;
    24  import java.util.Iterator;
    25  import java.util.Collection;
    26  import java.util.Collections;
    27
    28  /***
    29   * <p>Decorates a {@link DynaBean} to provide <code>Map</code> behaviour.</p>
    30   *
    31   * <p>The motivation for this implementation is to provide access to {@link DynaBean}
    32   *    properties in technologies that are unaware of BeanUtils and {@link DynaBean}s -
    33   *    such as the expression languages of JSTL and JSF.</p>
    34   *
    35   * <p>This can be achieved either by wrapping the {@link DynaBean} prior to
    36   *    providing it to the technolody to process or by providing a <code>Map</code>
    37   *    accessor method on the DynaBean implementation:
    38   *    <pre><code>
    39   *         public Map getMap() {
    40   *             return new DynaBeanMapDecorator(this);
    41   *         }</code></pre>
    42   *   </ul>
    43   * </p>
    44   *
    45   * <p>This, for example, could be used in JSTL in the following way to access
    46   *    a DynaBean's <code>fooProperty</code>:
    47   *    <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul>
    48   * </p>
    49   *
    50   * <h3>Usage</h3>
    51   *
    52   * <p>To decorate a {@link DynaBean} simply instantiate this class with the
    53   *    target {@link DynaBean}:</p>
    54   *
    55   * <ul><li><code>Map fooMap = new DynaBeanMapDecorator(fooDynaBean);</code></li></ul>
    56   *
    57   * <p>The above example creates a <b><i>read only</i></b> <code>Map</code>.
    58   *    To create  a <code>Map</code> which can be modified, construct a
    59   *    <code>DynaBeanMapDecorator</code> with the <b><i>read only</i></b>
    60   *    attribute set to <code>false</code>:</p>
    61   *
    62   * <ul><li><code>Map fooMap = new DynaBeanMapDecorator(fooDynaBean, false);</code></li></ul>
    63   *
    64   * <h3>Limitations</h3>
    65   * <p>In this implementation the <code>entrySet()</code>, <code>keySet()</code>
    66   *    and <code>values()</code> methods create an <b><i>unmodifiable</i></b>
    67   *    <code>Set</code> and it does not support the Map's <code>clear()</code>
    68   *    and <code>remove()</code> operations.</p>
    69   *
    70   * @since BeanUtils 1.8.0
    71   * @version $Revision: 546471 $ $Date: 2007-06-12 13:57:20 +0100 (Tue, 12 Jun 2007) $
    72   */
    73  public class DynaBeanMapDecorator implements Map {
    74
    75      private DynaBean dynaBean;
    76      private boolean readOnly;
    77      private transient Set keySet;
    78
    79      // ------------------- Constructors ----------------------------------
    80
    81      /***
    82       * Constructs a  read only Map for the specified
    83       * {@link DynaBean}.
    84       *
    85       * @param dynaBean The dyna bean being decorated
    86       * @throws IllegalArgumentException if the {@link DynaBean} is null.
    87       */
    88      public DynaBeanMapDecorator(DynaBean dynaBean) {
    89          this(dynaBean, true);
    90      }
    91
    92      /***
    93       * Construct a Map for the specified {@link DynaBean}.
    94       *
    95       * @param dynaBean The dyna bean being decorated
    96       * @param readOnly <code>true</code> if the Mpa is read only
    97       * otherwise <code>false</code>
    98       * @throws IllegalArgumentException if the {@link DynaBean} is null.
    99       */
    100     public DynaBeanMapDecorator(DynaBean dynaBean, boolean readOnly) {
    101         if (dynaBean == null) {
    102             throw new IllegalArgumentException("DynaBean is null");
    103         }
    104         this.dynaBean = dynaBean;
    105         this.readOnly = readOnly;
    106     }
    107
    108
    109     // ------------------- public Methods --------------------------------
    110
    111
    112     /***
    113      * Indicate whether the Map is read only.
    114      *
    115      * @return <code>true</code> if the Map is read only,
    116      * otherwise <code>false</code>.
    117      */
    118     public boolean isReadOnly() {
    119         return readOnly;
    120     }
    121
    122     // ------------------- java.util.Map Methods -------------------------
    123
    124     /***
    125      * clear() operation is not supported.
    126      *
    127      * @throws UnsupportedOperationException
    128      */
    129     public void clear() {
    130         throw new UnsupportedOperationException();
    131     }
    132
    133     /***
    134      * Indicate whether the {@link DynaBean} contains a specified
    135      * value for one (or more) of its properties.
    136      *
    137      * @param key The {@link DynaBean}'s property name
    138      * @return <code>true</code> if one of the {@link DynaBean}'s
    139      * properties contains a specified value.
    140      */
    141     public boolean containsKey(Object key) {
    142         DynaClass dynaClass = getDynaBean().getDynaClass();
    143         DynaProperty dynaProperty = dynaClass.getDynaProperty(toString(key));
    144         return (dynaProperty == null ? false : true);
    145     }
    146
    147     /***
    148      * Indicates whether the decorated {@link DynaBean} contains
    149      * a specified value.
    150      *
    151      * @param value The value to check for.
    152      * @return <code>true</code> if one of the the {@link DynaBean}'s
    153      * properties contains the specified value, otherwise
    154      * <code>false</code>.
    155      */
    156     public boolean containsValue(Object value) {
    157         DynaProperty[] properties = getDynaProperties();
    158         for (int i = 0; i < properties.length; i++) {
    159             String key = properties[i].getName();
    160             Object prop = getDynaBean().get(key);
    161             if (value == null) {
    162                 if (prop == null) {
    163                     return true;
    164                 }
    165             } else {
    166                 if (value.equals(prop)) {
    167                     return true;
    168                 }
    169             }
    170         }
    171         return false;
    172     }
    173
    174     /***
    175      * <p>Returns the Set of the property/value mappings
    176      * in the decorated {@link DynaBean}.</p>
    177      *
    178      * <p>Each element in the Set is a <code>Map.Entry</code>
    179      * type.</p>
    180      *
    181      * @return An unmodifiable set of the DynaBean
    182      * property name/value pairs
    183      */
    184     public Set entrySet() {
    185         DynaProperty[] properties = getDynaProperties();
    186         Set set = new HashSet(properties.length);
    187         for (int i = 0; i < properties.length; i++) {
    188             String key = properties[i].getName();
    189             Object value = getDynaBean().get(key);
    190             set.add(new MapEntry(key, value));
    191         }
    192         return Collections.unmodifiableSet(set);
    193     }
    194
    195     /***
    196      * Return the value for the specified key from
    197      * the decorated {@link DynaBean}.
    198      *
    199      * @param key The {@link DynaBean}'s property name
    200      * @return The value for the specified property.
    201      */
    202     public Object get(Object key) {
    203         return getDynaBean().get(toString(key));
    204     }
    205
    206     /***
    207      * Indicate whether the decorated {@link DynaBean} has
    208      * any properties.
    209      *
    210      * @return <code>true</code> if the {@link DynaBean} has
    211      * no properties, otherwise <code>false</code>.
    212      */
    213     public boolean isEmpty() {
    214         return (getDynaProperties().length == 0);
    215     }
    216
    217     /***
    218      * <p>Returns the Set of the property
    219      * names in the decorated {@link DynaBean}.</p>
    220      *
    221      * <p><b>N.B.</b>For {@link DynaBean}s whose associated {@link DynaClass}
    222      * is a {@link MutableDynaClass} a new Set is created every
    223      * time, otherwise the Set is created only once and cached.</p>
    224      *
    225      * @return An unmodifiable set of the {@link DynaBean}s
    226      * property names.
    227      */
    228     public Set keySet() {
    229         if (keySet != null) {
    230             return keySet;
    231         }
    232
    233         // Create a Set of the keys
    234         DynaProperty[] properties = getDynaProperties();
    235         Set set = new HashSet(properties.length);
    236         for (int i = 0; i < properties.length; i++) {
    237             set.add(properties[i].getName());
    238         }
    239         set = Collections.unmodifiableSet(set);
    240
    241         // Cache the keySet if Not a MutableDynaClass
    242         DynaClass dynaClass = getDynaBean().getDynaClass();
    243         if (!(dynaClass instanceof MutableDynaClass)) {
    244             keySet = set;
    245         }
    246
    247         return set;
    248
    249     }
    250
    251     /***
    252      * Set the value for the specified property in
    253      * the decorated {@link DynaBean}.
    254      *
    255      * @param key The {@link DynaBean}'s property name
    256      * @param value The value for the specified property.
    257      * @return The previous property's value.
    258      * @throws UnsupportedOperationException if
    259      * <code>isReadOnly()</code> is true.
    260      */
    261     public Object put(Object key, Object value) {
    262         if (isReadOnly()) {
    263             throw new UnsupportedOperationException("Map is read only");
    264         }
    265         String property = toString(key);
    266         Object previous = getDynaBean().get(property);
    267         getDynaBean().set(property, value);
    268         return previous;
    269     }
    270
    271     /***
    272      * Copy the contents of a Map to the decorated {@link DynaBean}.
    273      *
    274      * @param map The Map of values to copy.
    275      * @throws UnsupportedOperationException if
    276      * <code>isReadOnly()</code> is true.
    277      */
    278     public void putAll(Map map) {
    279         if (isReadOnly()) {
    280             throw new UnsupportedOperationException("Map is read only");
    281         }
    282         Iterator keys = map.keySet().iterator();
    283         while (keys.hasNext()) {
    284             Object key = keys.next();
    285             put(key, map.get(key));
    286         }
    287     }
    288
    289     /***
    290      * remove() operation is not supported.
    291      *
    292      * @param key The {@link DynaBean}'s property name
    293      * @return the value removed
    294      * @throws UnsupportedOperationException
    295      */
    296     public Object remove(Object key) {
    297         throw new UnsupportedOperationException();
    298     }
    299
    300     /***
    301      * Returns the number properties in the decorated
    302      * {@link DynaBean}.
    303      * @return The number of properties.
    304      */
    305     public int size() {
    306         return getDynaProperties().length;
    307     }
    308
    309     /***
    310      * Returns the set of property values in the
    311      * decorated {@link DynaBean}.
    312      *
    313      * @return Unmodifiable collection of values.
    314      */
    315     public Collection values() {
    316         DynaProperty[] properties = getDynaProperties();
    317         List values = new ArrayList(properties.length);
    318         for (int i = 0; i < properties.length; i++) {
    319             String key = properties[i].getName();
    320             Object value = getDynaBean().get(key);
    321             values.add(value);
    322         }
    323         return Collections.unmodifiableList(values);
    324     }
    325
    326     // ------------------- protected Methods -----------------------------
    327
    328     /***
    329      * Provide access to the underlying {@link DynaBean}
    330      * this Map decorates.
    331      *
    332      * @return the decorated {@link DynaBean}.
    333      */
    334     public DynaBean getDynaBean() {
    335         return dynaBean;
    336     }
    337
    338     // ------------------- private Methods -------------------------------
    339
    340     /***
    341      * Convenience method to retrieve the {@link DynaProperty}s
    342      * for this {@link DynaClass}.
    343      *
    344      * @return The an array of the {@link DynaProperty}s.
    345      */
    346     private DynaProperty[] getDynaProperties() {
    347         return getDynaBean().getDynaClass().getDynaProperties();
    348     }
    349
    350     /***
    351      * Convenience method to convert an Object
    352      * to a String.
    353      *
    354      * @param obj The Object to convert
    355      * @return String representation of the object
    356      */
    357     private String toString(Object obj) {
    358         return (obj == null ? null : obj.toString());
    359     }
    360
    361     /***
    362      * Map.Entry implementation.
    363      */
    364     private static class MapEntry implements Map.Entry {
    365         private Object key;
    366         private Object value;
    367         MapEntry(Object key, Object value) {
    368             this.key = key;
    369             this.value = value;
    370         }
    371         public boolean equals(Object o) {
    372             if (!(o instanceof Map.Entry)) {
    373                 return false;
    374             }
    375             Map.Entry e = (Map.Entry)o;
    376             return ((key.equals(e.getKey())) &&
    377                     (value == null ? e.getValue() == null
    378                                    : value.equals(e.getValue())));
    379         }
    380         public int hashCode() {
    381             return key.hashCode() + (value == null ? 0 : value.hashCode());
    382         }
    383         public Object getKey() {
    384             return key;
    385         }
    386         public Object getValue() {
    387             return value;
    388         }
    389         public Object setValue(Object value) {
    390             throw new UnsupportedOperationException();
    391         }
    392     }
    393
    394 }