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 }