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.util.Map;
020import java.util.List;
021import java.util.ArrayList;
022import java.util.Set;
023import java.util.HashSet;
024import java.util.Iterator;
025import java.util.Collection;
026import 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 */
073public 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}