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.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  /**
28   * <p>A base class for decorators providing <code>Map</code> behavior on
29   * {@link DynaBean}s.</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 rather technical base class implements the methods of the
36   *    {@code Map} interface on top of a {@code DynaBean}. It was introduced
37   *    to handle generic parameters in a meaningful way without breaking
38   *    backwards compatibility of the {@link DynaBeanMapDecorator} class: A
39   *    map wrapping a {@code DynaBean} should be of type {@code Map<String, Object>}.
40   *    However, when using these generic parameters in {@code DynaBeanMapDecorator}
41   *    this would be an incompatible change (as method signatures would have to
42   *    be adapted). To solve this problem, this generic base class is added
43   *    which allows specifying the key type as parameter. This makes it easy to
44   *    have a new subclass using the correct generic parameters while
45   *    {@code DynaBeanMapDecorator} could still remain with compatible
46   *    parameters.</p>
47   *
48   * @param <K> the type of the keys in the decorated map
49   * @since BeanUtils 1.9.0
50   * @version $Id$
51   */
52  public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> {
53  
54      private final DynaBean dynaBean;
55      private final boolean readOnly;
56      private transient Set<K> keySet;
57  
58      // ------------------- Constructors ----------------------------------
59  
60      /**
61       * Constructs a read only Map for the specified
62       * {@link DynaBean}.
63       *
64       * @param dynaBean The dyna bean being decorated
65       * @throws IllegalArgumentException if the {@link DynaBean} is null.
66       */
67      public BaseDynaBeanMapDecorator(final DynaBean dynaBean) {
68          this(dynaBean, true);
69      }
70  
71      /**
72       * Construct a Map for the specified {@link DynaBean}.
73       *
74       * @param dynaBean The dyna bean being decorated
75       * @param readOnly <code>true</code> if the Map is read only
76       * otherwise <code>false</code>
77       * @throws IllegalArgumentException if the {@link DynaBean} is null.
78       */
79      public BaseDynaBeanMapDecorator(final DynaBean dynaBean, final boolean readOnly) {
80          if (dynaBean == null) {
81              throw new IllegalArgumentException("DynaBean is null");
82          }
83          this.dynaBean = dynaBean;
84          this.readOnly = readOnly;
85      }
86  
87  
88      // ------------------- public Methods --------------------------------
89  
90  
91      /**
92       * Indicate whether the Map is read only.
93       *
94       * @return <code>true</code> if the Map is read only,
95       * otherwise <code>false</code>.
96       */
97      public boolean isReadOnly() {
98          return readOnly;
99      }
100 
101     // ------------------- java.util.Map Methods -------------------------
102 
103     /**
104      * clear() operation is not supported.
105      *
106      * @throws UnsupportedOperationException
107      */
108     public void clear() {
109         throw new UnsupportedOperationException();
110     }
111 
112     /**
113      * Indicate whether the {@link DynaBean} contains a specified
114      * value for one (or more) of its properties.
115      *
116      * @param key The {@link DynaBean}'s property name
117      * @return <code>true</code> if one of the {@link DynaBean}'s
118      * properties contains a specified value.
119      */
120     public boolean containsKey(final Object key) {
121         final DynaClass dynaClass = getDynaBean().getDynaClass();
122         final DynaProperty dynaProperty = dynaClass.getDynaProperty(toString(key));
123         return (dynaProperty == null ? false : true);
124     }
125 
126     /**
127      * Indicates whether the decorated {@link DynaBean} contains
128      * a specified value.
129      *
130      * @param value The value to check for.
131      * @return <code>true</code> if one of the the {@link DynaBean}'s
132      * properties contains the specified value, otherwise
133      * <code>false</code>.
134      */
135     public boolean containsValue(final Object value) {
136         final DynaProperty[] properties = getDynaProperties();
137         for (DynaProperty propertie : properties) {
138             final String key = propertie.getName();
139             final Object prop = getDynaBean().get(key);
140             if (value == null) {
141                 if (prop == null) {
142                     return true;
143                 }
144             } else {
145                 if (value.equals(prop)) {
146                     return true;
147                 }
148             }
149         }
150         return false;
151     }
152 
153     /**
154      * <p>Returns the Set of the property/value mappings
155      * in the decorated {@link DynaBean}.</p>
156      *
157      * <p>Each element in the Set is a <code>Map.Entry</code>
158      * type.</p>
159      *
160      * @return An unmodifiable set of the DynaBean
161      * property name/value pairs
162      */
163     public Set<Map.Entry<K, Object>> entrySet() {
164         final DynaProperty[] properties = getDynaProperties();
165         final Set<Map.Entry<K, Object>> set = new HashSet<Map.Entry<K, Object>>(properties.length);
166         for (DynaProperty propertie : properties) {
167             final K key = convertKey(propertie.getName());
168             final Object value = getDynaBean().get(propertie.getName());
169             set.add(new MapEntry<K>(key, value));
170         }
171         return Collections.unmodifiableSet(set);
172     }
173 
174     /**
175      * Return the value for the specified key from
176      * the decorated {@link DynaBean}.
177      *
178      * @param key The {@link DynaBean}'s property name
179      * @return The value for the specified property.
180      */
181     public Object get(final Object key) {
182         return getDynaBean().get(toString(key));
183     }
184 
185     /**
186      * Indicate whether the decorated {@link DynaBean} has
187      * any properties.
188      *
189      * @return <code>true</code> if the {@link DynaBean} has
190      * no properties, otherwise <code>false</code>.
191      */
192     public boolean isEmpty() {
193         return (getDynaProperties().length == 0);
194     }
195 
196     /**
197      * <p>Returns the Set of the property
198      * names in the decorated {@link DynaBean}.</p>
199      *
200      * <p><b>N.B.</b>For {@link DynaBean}s whose associated {@link DynaClass}
201      * is a {@link MutableDynaClass} a new Set is created every
202      * time, otherwise the Set is created only once and cached.</p>
203      *
204      * @return An unmodifiable set of the {@link DynaBean}s
205      * property names.
206      */
207     public Set<K> keySet() {
208         if (keySet != null) {
209             return keySet;
210         }
211 
212         // Create a Set of the keys
213         final DynaProperty[] properties = getDynaProperties();
214         Set<K> set = new HashSet<K>(properties.length);
215         for (DynaProperty propertie : properties) {
216             set.add(convertKey(propertie.getName()));
217         }
218         set = Collections.unmodifiableSet(set);
219 
220         // Cache the keySet if Not a MutableDynaClass
221         final DynaClass dynaClass = getDynaBean().getDynaClass();
222         if (!(dynaClass instanceof MutableDynaClass)) {
223             keySet = set;
224         }
225 
226         return set;
227 
228     }
229 
230     /**
231      * Set the value for the specified property in
232      * the decorated {@link DynaBean}.
233      *
234      * @param key The {@link DynaBean}'s property name
235      * @param value The value for the specified property.
236      * @return The previous property's value.
237      * @throws UnsupportedOperationException if
238      * <code>isReadOnly()</code> is true.
239      */
240     public Object put(final K key, final Object value) {
241         if (isReadOnly()) {
242             throw new UnsupportedOperationException("Map is read only");
243         }
244         final String property = toString(key);
245         final Object previous = getDynaBean().get(property);
246         getDynaBean().set(property, value);
247         return previous;
248     }
249 
250     /**
251      * Copy the contents of a Map to the decorated {@link DynaBean}.
252      *
253      * @param map The Map of values to copy.
254      * @throws UnsupportedOperationException if
255      * <code>isReadOnly()</code> is true.
256      */
257     public void putAll(final Map<? extends K, ? extends Object> map) {
258         if (isReadOnly()) {
259             throw new UnsupportedOperationException("Map is read only");
260         }
261         for (final Map.Entry<? extends K, ?> e : map.entrySet()) {
262             put(e.getKey(), e.getValue());
263         }
264     }
265 
266     /**
267      * remove() operation is not supported.
268      *
269      * @param key The {@link DynaBean}'s property name
270      * @return the value removed
271      * @throws UnsupportedOperationException
272      */
273     public Object remove(final Object key) {
274         throw new UnsupportedOperationException();
275     }
276 
277     /**
278      * Returns the number properties in the decorated
279      * {@link DynaBean}.
280      * @return The number of properties.
281      */
282     public int size() {
283         return getDynaProperties().length;
284     }
285 
286     /**
287      * Returns the set of property values in the
288      * decorated {@link DynaBean}.
289      *
290      * @return Unmodifiable collection of values.
291      */
292     public Collection<Object> values() {
293         final DynaProperty[] properties = getDynaProperties();
294         final List<Object> values = new ArrayList<Object>(properties.length);
295         for (DynaProperty propertie : properties) {
296             final String key = propertie.getName();
297             final Object value = getDynaBean().get(key);
298             values.add(value);
299         }
300         return Collections.unmodifiableList(values);
301     }
302 
303     // ------------------- protected Methods -----------------------------
304 
305     /**
306      * Provide access to the underlying {@link DynaBean}
307      * this Map decorates.
308      *
309      * @return the decorated {@link DynaBean}.
310      */
311     public DynaBean getDynaBean() {
312         return dynaBean;
313     }
314 
315     /**
316      * Converts the name of a property to the key type of this decorator.
317      *
318      * @param propertyName the name of a property
319      * @return the converted key to be used in the decorated map
320      */
321     protected abstract K convertKey(String propertyName);
322 
323     // ------------------- private Methods -------------------------------
324 
325     /**
326      * Convenience method to retrieve the {@link DynaProperty}s
327      * for this {@link DynaClass}.
328      *
329      * @return The an array of the {@link DynaProperty}s.
330      */
331     private DynaProperty[] getDynaProperties() {
332         return getDynaBean().getDynaClass().getDynaProperties();
333     }
334 
335     /**
336      * Convenience method to convert an Object
337      * to a String.
338      *
339      * @param obj The Object to convert
340      * @return String representation of the object
341      */
342     private String toString(final Object obj) {
343         return (obj == null ? null : obj.toString());
344     }
345 
346     /**
347      * Map.Entry implementation.
348      */
349     private static class MapEntry<K> implements Map.Entry<K, Object> {
350         private final K key;
351         private final Object value;
352         MapEntry(final K key, final Object value) {
353             this.key = key;
354             this.value = value;
355         }
356         @Override
357         public boolean equals(final Object o) {
358             if (!(o instanceof Map.Entry)) {
359                 return false;
360             }
361             final Map.Entry<?, ?> e = (Map.Entry<?, ?>)o;
362             return ((key.equals(e.getKey())) &&
363                     (value == null ? e.getValue() == null
364                                    : value.equals(e.getValue())));
365         }
366         @Override
367         public int hashCode() {
368             return key.hashCode() + (value == null ? 0 : value.hashCode());
369         }
370         public K getKey() {
371             return key;
372         }
373         public Object getValue() {
374             return value;
375         }
376         public Object setValue(final Object value) {
377             throw new UnsupportedOperationException();
378         }
379     }
380 
381 }