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 08:57:20 -0400 (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 }