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: // ------------------- public Methods --------------------------------
109:
110: /**
111: * Indicate whether the Map is read only.
112: *
113: * @return <code>true</code> if the Map is read only,
114: * otherwise <code>false</code>.
115: */
116: public boolean isReadOnly() {
117: return readOnly;
118: }
119:
120: // ------------------- java.util.Map Methods -------------------------
121:
122: /**
123: * clear() operation is not supported.
124: *
125: * @throws UnsupportedOperationException
126: */
127: public void clear() {
128: throw new UnsupportedOperationException();
129: }
130:
131: /**
132: * Indicate whether the {@link DynaBean} contains a specified
133: * value for one (or more) of its properties.
134: *
135: * @param key The {@link DynaBean}'s property name
136: * @return <code>true</code> if one of the {@link DynaBean}'s
137: * properties contains a specified value.
138: */
139: public boolean containsKey(Object key) {
140: DynaClass dynaClass = getDynaBean().getDynaClass();
141: DynaProperty dynaProperty = dynaClass
142: .getDynaProperty(toString(key));
143: return (dynaProperty == null ? false : true);
144: }
145:
146: /**
147: * Indicates whether the decorated {@link DynaBean} contains
148: * a specified value.
149: *
150: * @param value The value to check for.
151: * @return <code>true</code> if one of the the {@link DynaBean}'s
152: * properties contains the specified value, otherwise
153: * <code>false</code>.
154: */
155: public boolean containsValue(Object value) {
156: DynaProperty[] properties = getDynaProperties();
157: for (int i = 0; i < properties.length; i++) {
158: String key = properties[i].getName();
159: Object prop = getDynaBean().get(key);
160: if (value == null) {
161: if (prop == null) {
162: return true;
163: }
164: } else {
165: if (value.equals(prop)) {
166: return true;
167: }
168: }
169: }
170: return false;
171: }
172:
173: /**
174: * <p>Returns the Set of the property/value mappings
175: * in the decorated {@link DynaBean}.</p>
176: *
177: * <p>Each element in the Set is a <code>Map.Entry</code>
178: * type.</p>
179: *
180: * @return An unmodifiable set of the DynaBean
181: * property name/value pairs
182: */
183: public Set entrySet() {
184: DynaProperty[] properties = getDynaProperties();
185: Set set = new HashSet(properties.length);
186: for (int i = 0; i < properties.length; i++) {
187: String key = properties[i].getName();
188: Object value = getDynaBean().get(key);
189: set.add(new MapEntry(key, value));
190: }
191: return Collections.unmodifiableSet(set);
192: }
193:
194: /**
195: * Return the value for the specified key from
196: * the decorated {@link DynaBean}.
197: *
198: * @param key The {@link DynaBean}'s property name
199: * @return The value for the specified property.
200: */
201: public Object get(Object key) {
202: return getDynaBean().get(toString(key));
203: }
204:
205: /**
206: * Indicate whether the decorated {@link DynaBean} has
207: * any properties.
208: *
209: * @return <code>true</code> if the {@link DynaBean} has
210: * no properties, otherwise <code>false</code>.
211: */
212: public boolean isEmpty() {
213: return (getDynaProperties().length == 0);
214: }
215:
216: /**
217: * <p>Returns the Set of the property
218: * names in the decorated {@link DynaBean}.</p>
219: *
220: * <p><b>N.B.</b>For {@link DynaBean}s whose associated {@link DynaClass}
221: * is a {@link MutableDynaClass} a new Set is created every
222: * time, otherwise the Set is created only once and cached.</p>
223: *
224: * @return An unmodifiable set of the {@link DynaBean}s
225: * property names.
226: */
227: public Set keySet() {
228: if (keySet != null) {
229: return keySet;
230: }
231:
232: // Create a Set of the keys
233: DynaProperty[] properties = getDynaProperties();
234: Set set = new HashSet(properties.length);
235: for (int i = 0; i < properties.length; i++) {
236: set.add(properties[i].getName());
237: }
238: set = Collections.unmodifiableSet(set);
239:
240: // Cache the keySet if Not a MutableDynaClass
241: DynaClass dynaClass = getDynaBean().getDynaClass();
242: if (!(dynaClass instanceof MutableDynaClass)) {
243: keySet = set;
244: }
245:
246: return set;
247:
248: }
249:
250: /**
251: * Set the value for the specified property in
252: * the decorated {@link DynaBean}.
253: *
254: * @param key The {@link DynaBean}'s property name
255: * @param value The value for the specified property.
256: * @return The previous property's value.
257: * @throws UnsupportedOperationException if
258: * <code>isReadOnly()</code> is true.
259: */
260: public Object put(Object key, Object value) {
261: if (isReadOnly()) {
262: throw new UnsupportedOperationException("Map is read only");
263: }
264: String property = toString(key);
265: Object previous = getDynaBean().get(property);
266: getDynaBean().set(property, value);
267: return previous;
268: }
269:
270: /**
271: * Copy the contents of a Map to the decorated {@link DynaBean}.
272: *
273: * @param map The Map of values to copy.
274: * @throws UnsupportedOperationException if
275: * <code>isReadOnly()</code> is true.
276: */
277: public void putAll(Map map) {
278: if (isReadOnly()) {
279: throw new UnsupportedOperationException("Map is read only");
280: }
281: Iterator keys = map.keySet().iterator();
282: while (keys.hasNext()) {
283: Object key = keys.next();
284: put(key, map.get(key));
285: }
286: }
287:
288: /**
289: * remove() operation is not supported.
290: *
291: * @param key The {@link DynaBean}'s property name
292: * @return the value removed
293: * @throws UnsupportedOperationException
294: */
295: public Object remove(Object key) {
296: throw new UnsupportedOperationException();
297: }
298:
299: /**
300: * Returns the number properties in the decorated
301: * {@link DynaBean}.
302: * @return The number of properties.
303: */
304: public int size() {
305: return getDynaProperties().length;
306: }
307:
308: /**
309: * Returns the set of property values in the
310: * decorated {@link DynaBean}.
311: *
312: * @return Unmodifiable collection of values.
313: */
314: public Collection values() {
315: DynaProperty[] properties = getDynaProperties();
316: List values = new ArrayList(properties.length);
317: for (int i = 0; i < properties.length; i++) {
318: String key = properties[i].getName();
319: Object value = getDynaBean().get(key);
320: values.add(value);
321: }
322: return Collections.unmodifiableList(values);
323: }
324:
325: // ------------------- protected Methods -----------------------------
326:
327: /**
328: * Provide access to the underlying {@link DynaBean}
329: * this Map decorates.
330: *
331: * @return the decorated {@link DynaBean}.
332: */
333: public DynaBean getDynaBean() {
334: return dynaBean;
335: }
336:
337: // ------------------- private Methods -------------------------------
338:
339: /**
340: * Convenience method to retrieve the {@link DynaProperty}s
341: * for this {@link DynaClass}.
342: *
343: * @return The an array of the {@link DynaProperty}s.
344: */
345: private DynaProperty[] getDynaProperties() {
346: return getDynaBean().getDynaClass().getDynaProperties();
347: }
348:
349: /**
350: * Convenience method to convert an Object
351: * to a String.
352: *
353: * @param obj The Object to convert
354: * @return String representation of the object
355: */
356: private String toString(Object obj) {
357: return (obj == null ? null : obj.toString());
358: }
359:
360: /**
361: * Map.Entry implementation.
362: */
363: private static class MapEntry implements Map.Entry {
364: private Object key;
365: private Object value;
366:
367: MapEntry(Object key, Object value) {
368: this .key = key;
369: this .value = value;
370: }
371:
372: public boolean equals(Object o) {
373: if (!(o instanceof Map.Entry)) {
374: return false;
375: }
376: Map.Entry e = (Map.Entry) o;
377: return ((key.equals(e.getKey())) && (value == null ? e
378: .getValue() == null : value.equals(e.getValue())));
379: }
380:
381: public int hashCode() {
382: return key.hashCode()
383: + (value == null ? 0 : value.hashCode());
384: }
385:
386: public Object getKey() {
387: return key;
388: }
389:
390: public Object getValue() {
391: return value;
392: }
393:
394: public Object setValue(Object value) {
395: throw new UnsupportedOperationException();
396: }
397: }
398:
399: }
|