1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.commons.beanutils;
20
21
22 import java.io.Serializable;
23 import java.lang.reflect.Array;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27
28
29 /***
30 * <p>Minimal implementation of the <code>DynaBean</code> interface. Can be
31 * used as a convenience base class for more sophisticated implementations.</p>
32 *
33 * <p><strong>IMPLEMENTATION NOTE</strong> - Instances of this class that are
34 * accessed from multiple threads simultaneously need to be synchronized.</p>
35 *
36 * <p><strong>IMPLEMENTATION NOTE</strong> - Instances of this class can be
37 * successfully serialized and deserialized <strong>ONLY</strong> if all
38 * property values are <code>Serializable</code>.</p>
39 *
40 * @author Craig McClanahan
41 * @version $Revision: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $
42 */
43
44 public class BasicDynaBean implements DynaBean, Serializable {
45
46
47
48
49
50 /***
51 * Construct a new <code>DynaBean</code> associated with the specified
52 * <code>DynaClass</code> instance.
53 *
54 * @param dynaClass The DynaClass we are associated with
55 */
56 public BasicDynaBean(DynaClass dynaClass) {
57
58 super();
59 this.dynaClass = dynaClass;
60
61 }
62
63
64
65
66
67 /***
68 * The <code>DynaClass</code> "base class" that this DynaBean
69 * is associated with.
70 */
71 protected DynaClass dynaClass = null;
72
73
74 /***
75 * The set of property values for this DynaBean, keyed by property name.
76 */
77 protected HashMap values = new HashMap();
78
79 /*** Map decorator for this DynaBean */
80 private transient Map mapDecorator;
81
82 /***
83 * Return a Map representation of this DynaBean.
84 * </p>
85 * This, for example, could be used in JSTL in the following way to access
86 * a DynaBean's <code>fooProperty</code>:
87 * <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul>
88 *
89 * @return a Map representation of this DynaBean
90 */
91 public Map getMap() {
92
93
94 if (mapDecorator == null) {
95 mapDecorator = new DynaBeanMapDecorator(this);
96 }
97 return mapDecorator;
98
99 }
100
101
102
103
104 /***
105 * Does the specified mapped property contain a value for the specified
106 * key value?
107 *
108 * @param name Name of the property to check
109 * @param key Name of the key to check
110 * @return <code>true<code> if the mapped property contains a value for
111 * the specified key, otherwise <code>false</code>
112 *
113 * @exception IllegalArgumentException if there is no property
114 * of the specified name
115 */
116 public boolean contains(String name, String key) {
117
118 Object value = values.get(name);
119 if (value == null) {
120 throw new NullPointerException
121 ("No mapped value for '" + name + "(" + key + ")'");
122 } else if (value instanceof Map) {
123 return (((Map) value).containsKey(key));
124 } else {
125 throw new IllegalArgumentException
126 ("Non-mapped property for '" + name + "(" + key + ")'");
127 }
128
129 }
130
131
132 /***
133 * Return the value of a simple property with the specified name.
134 *
135 * @param name Name of the property whose value is to be retrieved
136 * @return The property's value
137 *
138 * @exception IllegalArgumentException if there is no property
139 * of the specified name
140 */
141 public Object get(String name) {
142
143
144 Object value = values.get(name);
145 if (value != null) {
146 return (value);
147 }
148
149
150 Class type = getDynaProperty(name).getType();
151 if (!type.isPrimitive()) {
152 return (value);
153 }
154
155
156 if (type == Boolean.TYPE) {
157 return (Boolean.FALSE);
158 } else if (type == Byte.TYPE) {
159 return (new Byte((byte) 0));
160 } else if (type == Character.TYPE) {
161 return (new Character((char) 0));
162 } else if (type == Double.TYPE) {
163 return (new Double(0.0));
164 } else if (type == Float.TYPE) {
165 return (new Float((float) 0.0));
166 } else if (type == Integer.TYPE) {
167 return (new Integer(0));
168 } else if (type == Long.TYPE) {
169 return (new Long(0));
170 } else if (type == Short.TYPE) {
171 return (new Short((short) 0));
172 } else {
173 return (null);
174 }
175
176 }
177
178
179 /***
180 * Return the value of an indexed property with the specified name.
181 *
182 * @param name Name of the property whose value is to be retrieved
183 * @param index Index of the value to be retrieved
184 * @return The indexed property's value
185 *
186 * @exception IllegalArgumentException if there is no property
187 * of the specified name
188 * @exception IllegalArgumentException if the specified property
189 * exists, but is not indexed
190 * @exception IndexOutOfBoundsException if the specified index
191 * is outside the range of the underlying property
192 * @exception NullPointerException if no array or List has been
193 * initialized for this property
194 */
195 public Object get(String name, int index) {
196
197 Object value = values.get(name);
198 if (value == null) {
199 throw new NullPointerException
200 ("No indexed value for '" + name + "[" + index + "]'");
201 } else if (value.getClass().isArray()) {
202 return (Array.get(value, index));
203 } else if (value instanceof List) {
204 return ((List) value).get(index);
205 } else {
206 throw new IllegalArgumentException
207 ("Non-indexed property for '" + name + "[" + index + "]'");
208 }
209
210 }
211
212
213 /***
214 * Return the value of a mapped property with the specified name,
215 * or <code>null</code> if there is no value for the specified key.
216 *
217 * @param name Name of the property whose value is to be retrieved
218 * @param key Key of the value to be retrieved
219 * @return The mapped property's value
220 *
221 * @exception IllegalArgumentException if there is no property
222 * of the specified name
223 * @exception IllegalArgumentException if the specified property
224 * exists, but is not mapped
225 */
226 public Object get(String name, String key) {
227
228 Object value = values.get(name);
229 if (value == null) {
230 throw new NullPointerException
231 ("No mapped value for '" + name + "(" + key + ")'");
232 } else if (value instanceof Map) {
233 return (((Map) value).get(key));
234 } else {
235 throw new IllegalArgumentException
236 ("Non-mapped property for '" + name + "(" + key + ")'");
237 }
238
239 }
240
241
242 /***
243 * Return the <code>DynaClass</code> instance that describes the set of
244 * properties available for this DynaBean.
245 *
246 * @return The associated DynaClass
247 */
248 public DynaClass getDynaClass() {
249
250 return (this.dynaClass);
251
252 }
253
254
255 /***
256 * Remove any existing value for the specified key on the
257 * specified mapped property.
258 *
259 * @param name Name of the property for which a value is to
260 * be removed
261 * @param key Key of the value to be removed
262 *
263 * @exception IllegalArgumentException if there is no property
264 * of the specified name
265 */
266 public void remove(String name, String key) {
267
268 Object value = values.get(name);
269 if (value == null) {
270 throw new NullPointerException
271 ("No mapped value for '" + name + "(" + key + ")'");
272 } else if (value instanceof Map) {
273 ((Map) value).remove(key);
274 } else {
275 throw new IllegalArgumentException
276 ("Non-mapped property for '" + name + "(" + key + ")'");
277 }
278
279 }
280
281
282 /***
283 * Set the value of a simple property with the specified name.
284 *
285 * @param name Name of the property whose value is to be set
286 * @param value Value to which this property is to be set
287 *
288 * @exception ConversionException if the specified value cannot be
289 * converted to the type required for this property
290 * @exception IllegalArgumentException if there is no property
291 * of the specified name
292 * @exception NullPointerException if an attempt is made to set a
293 * primitive property to null
294 */
295 public void set(String name, Object value) {
296
297 DynaProperty descriptor = getDynaProperty(name);
298 if (value == null) {
299 if (descriptor.getType().isPrimitive()) {
300 throw new NullPointerException
301 ("Primitive value for '" + name + "'");
302 }
303 } else if (!isAssignable(descriptor.getType(), value.getClass())) {
304 throw new ConversionException
305 ("Cannot assign value of type '" +
306 value.getClass().getName() +
307 "' to property '" + name + "' of type '" +
308 descriptor.getType().getName() + "'");
309 }
310 values.put(name, value);
311
312 }
313
314
315 /***
316 * Set the value of an indexed property with the specified name.
317 *
318 * @param name Name of the property whose value is to be set
319 * @param index Index of the property to be set
320 * @param value Value to which this property is to be set
321 *
322 * @exception ConversionException if the specified value cannot be
323 * converted to the type required for this property
324 * @exception IllegalArgumentException if there is no property
325 * of the specified name
326 * @exception IllegalArgumentException if the specified property
327 * exists, but is not indexed
328 * @exception IndexOutOfBoundsException if the specified index
329 * is outside the range of the underlying property
330 */
331 public void set(String name, int index, Object value) {
332
333 Object prop = values.get(name);
334 if (prop == null) {
335 throw new NullPointerException
336 ("No indexed value for '" + name + "[" + index + "]'");
337 } else if (prop.getClass().isArray()) {
338 Array.set(prop, index, value);
339 } else if (prop instanceof List) {
340 try {
341 ((List) prop).set(index, value);
342 } catch (ClassCastException e) {
343 throw new ConversionException(e.getMessage());
344 }
345 } else {
346 throw new IllegalArgumentException
347 ("Non-indexed property for '" + name + "[" + index + "]'");
348 }
349
350 }
351
352
353 /***
354 * Set the value of a mapped property with the specified name.
355 *
356 * @param name Name of the property whose value is to be set
357 * @param key Key of the property to be set
358 * @param value Value to which this property is to be set
359 *
360 * @exception ConversionException if the specified value cannot be
361 * converted to the type required for this property
362 * @exception IllegalArgumentException if there is no property
363 * of the specified name
364 * @exception IllegalArgumentException if the specified property
365 * exists, but is not mapped
366 */
367 public void set(String name, String key, Object value) {
368
369 Object prop = values.get(name);
370 if (prop == null) {
371 throw new NullPointerException
372 ("No mapped value for '" + name + "(" + key + ")'");
373 } else if (prop instanceof Map) {
374 ((Map) prop).put(key, value);
375 } else {
376 throw new IllegalArgumentException
377 ("Non-mapped property for '" + name + "(" + key + ")'");
378 }
379
380 }
381
382
383
384
385
386 /***
387 * Return the property descriptor for the specified property name.
388 *
389 * @param name Name of the property for which to retrieve the descriptor
390 * @return The property descriptor
391 *
392 * @exception IllegalArgumentException if this is not a valid property
393 * name for our DynaClass
394 */
395 protected DynaProperty getDynaProperty(String name) {
396
397 DynaProperty descriptor = getDynaClass().getDynaProperty(name);
398 if (descriptor == null) {
399 throw new IllegalArgumentException
400 ("Invalid property name '" + name + "'");
401 }
402 return (descriptor);
403
404 }
405
406
407 /***
408 * Is an object of the source class assignable to the destination class?
409 *
410 * @param dest Destination class
411 * @param source Source class
412 * @return <code>true</code> if the source class is assignable to the
413 * destination class, otherwise <code>false</code>
414 */
415 protected boolean isAssignable(Class dest, Class source) {
416
417 if (dest.isAssignableFrom(source) ||
418 ((dest == Boolean.TYPE) && (source == Boolean.class)) ||
419 ((dest == Byte.TYPE) && (source == Byte.class)) ||
420 ((dest == Character.TYPE) && (source == Character.class)) ||
421 ((dest == Double.TYPE) && (source == Double.class)) ||
422 ((dest == Float.TYPE) && (source == Float.class)) ||
423 ((dest == Integer.TYPE) && (source == Integer.class)) ||
424 ((dest == Long.TYPE) && (source == Long.class)) ||
425 ((dest == Short.TYPE) && (source == Short.class))) {
426 return (true);
427 } else {
428 return (false);
429 }
430
431 }
432
433
434 }