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    }