    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.beanutils;
    018
    019    import java.util.Map;
    020    import java.util.List;
    021    import java.util.ArrayList;
    022    import java.util.Set;
    023    import java.util.HashSet;
    024    import java.util.Iterator;
    025    import java.util.Collection;
    026    import java.util.Collections;
    027
    028    /**
    029     * <p>Decorates a {@link DynaBean} to provide <code>Map</code> behaviour.</p>
    030     *
    031     * <p>The motivation for this implementation is to provide access to {@link DynaBean}
    032     *    properties in technologies that are unaware of BeanUtils and {@link DynaBean}s -
    033     *    such as the expression languages of JSTL and JSF.</p>
    034     *
    035     * <p>This can be achieved either by wrapping the {@link DynaBean} prior to
    036     *    providing it to the technolody to process or by providing a <code>Map</code>
    037     *    accessor method on the DynaBean implementation:
    038     *    <pre><code>
    039     *         public Map getMap() {
    040     *             return new DynaBeanMapDecorator(this);
    041     *         }</code></pre>
    042     *   </ul>
    043     * </p>
    044     *
    045     * <p>This, for example, could be used in JSTL in the following way to access
    046     *    a DynaBean's <code>fooProperty</code>:
    047     *    <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul>
    048     * </p>
    049     *
    050     * <h3>Usage</h3>
    051     *
    052     * <p>To decorate a {@link DynaBean} simply instantiate this class with the
    053     *    target {@link DynaBean}:</p>
    054     *
    055     * <ul><li><code>Map fooMap = new DynaBeanMapDecorator(fooDynaBean);</code></li></ul>
    056     *
    057     * <p>The above example creates a <b><i>read only</i></b> <code>Map</code>.
    058     *    To create  a <code>Map</code> which can be modified, construct a
    059     *    <code>DynaBeanMapDecorator</code> with the <b><i>read only</i></b>
    060     *    attribute set to <code>false</code>:</p>
    061     *
    062     * <ul><li><code>Map fooMap = new DynaBeanMapDecorator(fooDynaBean, false);</code></li></ul>
    063     *
    064     * <h3>Limitations</h3>
    065     * <p>In this implementation the <code>entrySet()</code>, <code>keySet()</code>
    066     *    and <code>values()</code> methods create an <b><i>unmodifiable</i></b>
    067     *    <code>Set</code> and it does not support the Map's <code>clear()</code>
    068     *    and <code>remove()</code> operations.</p>
    069     *
    070     * @since BeanUtils 1.8.0
    071     * @version $Revision: 546471 $ $Date: 2007-06-12 13:57:20 +0100 (Tue, 12 Jun 2007) $
    072     */
    073    public class DynaBeanMapDecorator implements Map {
    074
    075        private DynaBean dynaBean;
    076        private boolean readOnly;
    077        private transient Set keySet;
    078
    079        // ------------------- Constructors ----------------------------------
    080
    081        /**
    082         * Constructs a  read only Map for the specified
    083         * {@link DynaBean}.
    084         *
    085         * @param dynaBean The dyna bean being decorated
    086         * @throws IllegalArgumentException if the {@link DynaBean} is null.
    087         */
    088        public DynaBeanMapDecorator(DynaBean dynaBean) {
    089            this(dynaBean, true);
    090        }
    091
    092        /**
    093         * Construct a Map for the specified {@link DynaBean}.
    094         *
    095         * @param dynaBean The dyna bean being decorated
    096         * @param readOnly <code>true</code> if the Mpa is read only
    097         * otherwise <code>false</code>
    098         * @throws IllegalArgumentException if the {@link DynaBean} is null.
    099         */
    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    }