1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 13:57:20 +0100 (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
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
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
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
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
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
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
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 }