Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
LazyDynaBean |
|
| 6.25;6.25 |
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.List; | |
20 | import java.util.ArrayList; | |
21 | import java.util.Map; | |
22 | import java.util.HashMap; | |
23 | import java.util.Date; | |
24 | import java.lang.reflect.Array; | |
25 | import java.math.BigDecimal; | |
26 | import java.math.BigInteger; | |
27 | import java.io.Serializable; | |
28 | import org.apache.commons.logging.Log; | |
29 | import org.apache.commons.logging.LogFactory; | |
30 | ||
31 | /** | |
32 | * <p>DynaBean which automatically adds properties to the <code>DynaClass</code> | |
33 | * and provides <i>Lazy List</i> and <i>Lazy Map</i> features.</p> | |
34 | * | |
35 | * <p>DynaBeans deal with three types of properties - <i>simple</i>, <i>indexed</i> and <i>mapped</i> and | |
36 | * have the following <code>get()</code> and <code>set()</code> methods for | |
37 | * each of these types:</p> | |
38 | * <ul> | |
39 | * <li><i>Simple</i> property methods - <code>get(name)</code> and | |
40 | * <code>set(name, value)</code></li> | |
41 | * <li><i>Indexed</i> property methods - <code>get(name, index)</code> and | |
42 | * <code>set(name, index, value)</code></li> | |
43 | * <li><i>Mapped</i> property methods - <code>get(name, key)</code> and | |
44 | * <code>set(name, key, value)</code></li> | |
45 | * </ul> | |
46 | * | |
47 | * <p><b><u>Getting Property Values</u></b></p> | |
48 | * <p>Calling any of the <code>get()</code> methods, for a property which | |
49 | * doesn't exist, returns <code>null</code> in this implementation.</p> | |
50 | * | |
51 | * <p><b><u>Setting Simple Properties</u></b></p> | |
52 | * <p>The <code>LazyDynaBean</code> will automatically add a property to the <code>DynaClass</code> | |
53 | * if it doesn't exist when the <code>set(name, value)</code> method is called.</p> | |
54 | * | |
55 | * <code>DynaBean myBean = new LazyDynaBean();</code></br> | |
56 | * <code>myBean.set("myProperty", "myValue");</code></br> | |
57 | * | |
58 | * <p><b><u>Setting Indexed Properties</u></b></p> | |
59 | * <p>If the property <b>doesn't</b> exist, the <code>LazyDynaBean</code> will automatically add | |
60 | * a property with an <code>ArrayList</code> type to the <code>DynaClass</code> when | |
61 | * the <code>set(name, index, value)</code> method is called. | |
62 | * It will also instantiate a new <code>ArrayList</code> and automatically <i>grow</i> | |
63 | * the <code>List</code> so that it is big enough to accomodate the index being set. | |
64 | * <code>ArrayList</code> is the default indexed property that LazyDynaBean uses but | |
65 | * this can be easily changed by overriding the <code>defaultIndexedProperty(name)</code> | |
66 | * method.</p> | |
67 | * | |
68 | * <code>DynaBean myBean = new LazyDynaBean();</code></br> | |
69 | * <code>myBean.set("myIndexedProperty", 0, "myValue1");</code></br> | |
70 | * <code>myBean.set("myIndexedProperty", 1, "myValue2");</code></br> | |
71 | * | |
72 | * <p>If the indexed property <b>does</b> exist in the <code>DynaClass</code> but is set to | |
73 | * <code>null</code> in the <code>LazyDynaBean</code>, then it will instantiate a | |
74 | * new <code>List</code> or <code>Array</code> as specified by the property's type | |
75 | * in the <code>DynaClass</code> and automatically <i>grow</i> the <code>List</code> | |
76 | * or <code>Array</code> so that it is big enough to accomodate the index being set.</p> | |
77 | * | |
78 | * <code>DynaBean myBean = new LazyDynaBean();</code></br> | |
79 | * <code>MutableDynaClass myClass = (MutableDynaClass)myBean.getDynaClass();</code></br> | |
80 | * <code>myClass.add("myIndexedProperty", int[].class);</code></br> | |
81 | * <code>myBean.set("myIndexedProperty", 0, new Integer(10));</code></br> | |
82 | * <code>myBean.set("myIndexedProperty", 1, new Integer(20));</code></br> | |
83 | * | |
84 | * <p><b><u>Setting Mapped Properties</u></b></p> | |
85 | * <p>If the property <b>doesn't</b> exist, the <code>LazyDynaBean</code> will automatically add | |
86 | * a property with a <code>HashMap</code> type to the <code>DynaClass</code> and | |
87 | * instantiate a new <code>HashMap</code> in the DynaBean when the | |
88 | * <code>set(name, key, value)</code> method is called. <code>HashMap</code> is the default | |
89 | * mapped property that LazyDynaBean uses but this can be easily changed by overriding | |
90 | * the <code>defaultMappedProperty(name)</code> method.</p> | |
91 | * | |
92 | * <code>DynaBean myBean = new LazyDynaBean();</code></br> | |
93 | * <code>myBean.set("myMappedProperty", "myKey", "myValue");</code></br> | |
94 | * | |
95 | * <p>If the mapped property <b>does</b> exist in the <code>DynaClass</code> but is set to | |
96 | * <code>null</code> in the <code>LazyDynaBean</code>, then it will instantiate a | |
97 | * new <code>Map</code> as specified by the property's type in the <code>DynaClass</code>.</p> | |
98 | * | |
99 | * <code>DynaBean myBean = new LazyDynaBean();</code></br> | |
100 | * <code>MutableDynaClass myClass = (MutableDynaClass)myBean.getDynaClass();</code></br> | |
101 | * <code>myClass.add("myMappedProperty", TreeMap.class);</code></br> | |
102 | * <code>myBean.set("myMappedProperty", "myKey", "myValue");</code></br> | |
103 | * | |
104 | * <p><b><u><i>Restricted</i> DynaClass</u></b></p> | |
105 | * <p><code>MutableDynaClass</code> have a facility to <i>restrict</i> the <code>DynaClass</code> | |
106 | * so that its properties cannot be modified. If the <code>MutableDynaClass</code> is | |
107 | * restricted then calling any of the <code>set()</code> methods for a property which | |
108 | * doesn't exist will result in a <code>IllegalArgumentException</code> being thrown.</p> | |
109 | * | |
110 | * @see LazyDynaClass | |
111 | * @author Niall Pemberton | |
112 | */ | |
113 | public class LazyDynaBean implements DynaBean, Serializable { | |
114 | ||
115 | ||
116 | /** | |
117 | * Commons Logging | |
118 | */ | |
119 | 91 | private transient Log logger = LogFactory.getLog(LazyDynaBean.class); |
120 | ||
121 | /** BigInteger Zero */ | |
122 | 1 | protected static final BigInteger BigInteger_ZERO = new BigInteger("0"); |
123 | /** BigDecimal Zero */ | |
124 | 1 | protected static final BigDecimal BigDecimal_ZERO = new BigDecimal("0"); |
125 | /** Character Space */ | |
126 | 1 | protected static final Character Character_SPACE = new Character(' '); |
127 | /** Byte Zero */ | |
128 | 1 | protected static final Byte Byte_ZERO = new Byte((byte)0); |
129 | /** Short Zero */ | |
130 | 1 | protected static final Short Short_ZERO = new Short((short)0); |
131 | /** Integer Zero */ | |
132 | 1 | protected static final Integer Integer_ZERO = new Integer(0); |
133 | /** Long Zero */ | |
134 | 1 | protected static final Long Long_ZERO = new Long(0); |
135 | /** Float Zero */ | |
136 | 1 | protected static final Float Float_ZERO = new Float((byte)0); |
137 | /** Double Zero */ | |
138 | 1 | protected static final Double Double_ZERO = new Double((byte)0); |
139 | ||
140 | /** | |
141 | * The <code>MutableDynaClass</code> "base class" that this DynaBean | |
142 | * is associated with. | |
143 | */ | |
144 | protected Map values; | |
145 | ||
146 | /** Map decorator for this DynaBean */ | |
147 | private transient Map mapDecorator; | |
148 | ||
149 | /** | |
150 | * The <code>MutableDynaClass</code> "base class" that this DynaBean | |
151 | * is associated with. | |
152 | */ | |
153 | protected MutableDynaClass dynaClass; | |
154 | ||
155 | ||
156 | // ------------------- Constructors ---------------------------------- | |
157 | ||
158 | /** | |
159 | * Construct a new <code>LazyDynaBean</code> with a <code>LazyDynaClass</code> instance. | |
160 | */ | |
161 | public LazyDynaBean() { | |
162 | 81 | this(new LazyDynaClass()); |
163 | 81 | } |
164 | ||
165 | /** | |
166 | * Construct a new <code>LazyDynaBean</code> with a <code>LazyDynaClass</code> instance. | |
167 | * | |
168 | * @param name Name of this DynaBean class | |
169 | */ | |
170 | public LazyDynaBean(String name) { | |
171 | 0 | this(new LazyDynaClass(name)); |
172 | 0 | } |
173 | ||
174 | /** | |
175 | * Construct a new <code>DynaBean</code> associated with the specified | |
176 | * <code>DynaClass</code> instance - if its not a <code>MutableDynaClass</code> | |
177 | * then a new <code>LazyDynaClass</code> is created and the properties copied. | |
178 | * | |
179 | * @param dynaClass The DynaClass we are associated with | |
180 | */ | |
181 | 91 | public LazyDynaBean(DynaClass dynaClass) { |
182 | ||
183 | 91 | values = newMap(); |
184 | ||
185 | 91 | if (dynaClass instanceof MutableDynaClass) { |
186 | 91 | this.dynaClass = (MutableDynaClass)dynaClass; |
187 | } else { | |
188 | 0 | this.dynaClass = new LazyDynaClass(dynaClass.getName(), dynaClass.getDynaProperties()); |
189 | } | |
190 | ||
191 | 91 | } |
192 | ||
193 | ||
194 | // ------------------- Public Methods ---------------------------------- | |
195 | ||
196 | /** | |
197 | * Return a Map representation of this DynaBean. | |
198 | * </p> | |
199 | * This, for example, could be used in JSTL in the following way to access | |
200 | * a DynaBean's <code>fooProperty</code>: | |
201 | * <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul> | |
202 | * | |
203 | * @return a Map representation of this DynaBean | |
204 | */ | |
205 | public Map getMap() { | |
206 | // cache the Map | |
207 | 0 | if (mapDecorator == null) { |
208 | 0 | mapDecorator = new DynaBeanMapDecorator(this); |
209 | } | |
210 | 0 | return mapDecorator; |
211 | } | |
212 | ||
213 | /** | |
214 | * <p>Return the size of an indexed or mapped property.</p> | |
215 | * | |
216 | * @param name Name of the property | |
217 | * @return The indexed or mapped property size | |
218 | * @exception IllegalArgumentException if no property name is specified | |
219 | */ | |
220 | public int size(String name) { | |
221 | ||
222 | 0 | if (name == null) { |
223 | 0 | throw new IllegalArgumentException("No property name specified"); |
224 | } | |
225 | ||
226 | 0 | Object value = values.get(name); |
227 | 0 | if (value == null) { |
228 | 0 | return 0; |
229 | } | |
230 | ||
231 | 0 | if (value instanceof Map) { |
232 | 0 | return ((Map)value).size(); |
233 | } | |
234 | ||
235 | 0 | if (value instanceof List) { |
236 | 0 | return ((List)value).size(); |
237 | } | |
238 | ||
239 | 0 | if ((value.getClass().isArray())) { |
240 | 0 | return Array.getLength(value); |
241 | } | |
242 | ||
243 | 0 | return 0; |
244 | ||
245 | } | |
246 | ||
247 | // ------------------- DynaBean Methods ---------------------------------- | |
248 | ||
249 | /** | |
250 | * Does the specified mapped property contain a value for the specified | |
251 | * key value? | |
252 | * | |
253 | * @param name Name of the property to check | |
254 | * @param key Name of the key to check | |
255 | * @return <code>true<code> if the mapped property contains a value for | |
256 | * the specified key, otherwise <code>false</code> | |
257 | * | |
258 | * @exception IllegalArgumentException if no property name is specified | |
259 | */ | |
260 | public boolean contains(String name, String key) { | |
261 | ||
262 | 0 | if (name == null) { |
263 | 0 | throw new IllegalArgumentException("No property name specified"); |
264 | } | |
265 | ||
266 | 0 | Object value = values.get(name); |
267 | 0 | if (value == null) { |
268 | 0 | return false; |
269 | } | |
270 | ||
271 | 0 | if (value instanceof Map) { |
272 | 0 | return (((Map) value).containsKey(key)); |
273 | } | |
274 | ||
275 | 0 | return false; |
276 | ||
277 | } | |
278 | ||
279 | /** | |
280 | * <p>Return the value of a simple property with the specified name.</p> | |
281 | * | |
282 | * <p><strong>N.B.</strong> Returns <code>null</code> if there is no property | |
283 | * of the specified name.</p> | |
284 | * | |
285 | * @param name Name of the property whose value is to be retrieved. | |
286 | * @return The property's value | |
287 | * @exception IllegalArgumentException if no property name is specified | |
288 | */ | |
289 | public Object get(String name) { | |
290 | ||
291 | 192 | if (name == null) { |
292 | 0 | throw new IllegalArgumentException("No property name specified"); |
293 | } | |
294 | ||
295 | // Value found | |
296 | 192 | Object value = values.get(name); |
297 | 192 | if (value != null) { |
298 | 155 | return value; |
299 | } | |
300 | ||
301 | // Property doesn't exist | |
302 | 37 | if (!isDynaProperty(name)) { |
303 | 28 | return null; |
304 | } | |
305 | ||
306 | // Property doesn't exist | |
307 | 9 | value = createProperty(name, dynaClass.getDynaProperty(name).getType()); |
308 | ||
309 | 9 | if (value != null) { |
310 | 5 | set(name, value); |
311 | } | |
312 | ||
313 | 9 | return value; |
314 | ||
315 | } | |
316 | ||
317 | /** | |
318 | * <p>Return the value of an indexed property with the specified name.</p> | |
319 | * | |
320 | * <p><strong>N.B.</strong> Returns <code>null</code> if there is no 'indexed' | |
321 | * property of the specified name.</p> | |
322 | * | |
323 | * @param name Name of the property whose value is to be retrieved | |
324 | * @param index Index of the value to be retrieved | |
325 | * @return The indexed property's value | |
326 | * | |
327 | * @exception IllegalArgumentException if the specified property | |
328 | * exists, but is not indexed | |
329 | * @exception IndexOutOfBoundsException if the specified index | |
330 | * is outside the range of the underlying property | |
331 | */ | |
332 | public Object get(String name, int index) { | |
333 | ||
334 | // If its not a property, then create default indexed property | |
335 | 32 | if (!isDynaProperty(name)) { |
336 | 4 | set(name, defaultIndexedProperty(name)); |
337 | } | |
338 | ||
339 | // Get the indexed property | |
340 | 32 | Object indexedProperty = get(name); |
341 | ||
342 | // Check that the property is indexed | |
343 | 32 | if (!dynaClass.getDynaProperty(name).isIndexed()) { |
344 | 0 | throw new IllegalArgumentException |
345 | ("Non-indexed property for '" + name + "[" + index + "]' " | |
346 | + dynaClass.getDynaProperty(name).getName()); | |
347 | } | |
348 | ||
349 | // Grow indexed property to appropriate size | |
350 | 32 | indexedProperty = growIndexedProperty(name, indexedProperty, index); |
351 | ||
352 | // Return the indexed value | |
353 | 32 | if (indexedProperty.getClass().isArray()) { |
354 | 16 | return Array.get(indexedProperty, index); |
355 | 16 | } else if (indexedProperty instanceof List) { |
356 | 16 | return ((List)indexedProperty).get(index); |
357 | } else { | |
358 | 0 | throw new IllegalArgumentException |
359 | ("Non-indexed property for '" + name + "[" + index + "]' " | |
360 | + indexedProperty.getClass().getName()); | |
361 | } | |
362 | ||
363 | } | |
364 | ||
365 | /** | |
366 | * <p>Return the value of a mapped property with the specified name.</p> | |
367 | * | |
368 | * <p><strong>N.B.</strong> Returns <code>null</code> if there is no 'mapped' | |
369 | * property of the specified name.</p> | |
370 | * | |
371 | * @param name Name of the property whose value is to be retrieved | |
372 | * @param key Key of the value to be retrieved | |
373 | * @return The mapped property's value | |
374 | * | |
375 | * @exception IllegalArgumentException if the specified property | |
376 | * exists, but is not mapped | |
377 | */ | |
378 | public Object get(String name, String key) { | |
379 | ||
380 | // If its not a property, then create default mapped property | |
381 | 14 | if (!isDynaProperty(name)) { |
382 | 4 | set(name, defaultMappedProperty(name)); |
383 | } | |
384 | ||
385 | // Get the mapped property | |
386 | 14 | Object mappedProperty = get(name); |
387 | ||
388 | // Check that the property is mapped | |
389 | 14 | if (!dynaClass.getDynaProperty(name).isMapped()) { |
390 | 0 | throw new IllegalArgumentException |
391 | ("Non-mapped property for '" + name + "(" + key + ")' " | |
392 | + dynaClass.getDynaProperty(name).getType().getName()); | |
393 | } | |
394 | ||
395 | // Get the value from the Map | |
396 | 14 | if (mappedProperty instanceof Map) { |
397 | 14 | return (((Map) mappedProperty).get(key)); |
398 | } else { | |
399 | 0 | throw new IllegalArgumentException |
400 | ("Non-mapped property for '" + name + "(" + key + ")'" | |
401 | + mappedProperty.getClass().getName()); | |
402 | } | |
403 | ||
404 | } | |
405 | ||
406 | ||
407 | /** | |
408 | * Return the <code>DynaClass</code> instance that describes the set of | |
409 | * properties available for this DynaBean. | |
410 | * | |
411 | * @return The associated DynaClass | |
412 | */ | |
413 | public DynaClass getDynaClass() { | |
414 | 65 | return dynaClass; |
415 | } | |
416 | ||
417 | /** | |
418 | * Remove any existing value for the specified key on the | |
419 | * specified mapped property. | |
420 | * | |
421 | * @param name Name of the property for which a value is to | |
422 | * be removed | |
423 | * @param key Key of the value to be removed | |
424 | * | |
425 | * @exception IllegalArgumentException if there is no property | |
426 | * of the specified name | |
427 | */ | |
428 | public void remove(String name, String key) { | |
429 | ||
430 | 0 | if (name == null) { |
431 | 0 | throw new IllegalArgumentException("No property name specified"); |
432 | } | |
433 | ||
434 | 0 | Object value = values.get(name); |
435 | 0 | if (value == null) { |
436 | 0 | return; |
437 | } | |
438 | ||
439 | 0 | if (value instanceof Map) { |
440 | 0 | ((Map) value).remove(key); |
441 | } else { | |
442 | 0 | throw new IllegalArgumentException |
443 | ("Non-mapped property for '" + name + "(" + key + ")'" | |
444 | + value.getClass().getName()); | |
445 | } | |
446 | ||
447 | 0 | } |
448 | ||
449 | /** | |
450 | * Set the value of a simple property with the specified name. | |
451 | * | |
452 | * @param name Name of the property whose value is to be set | |
453 | * @param value Value to which this property is to be set | |
454 | * | |
455 | * @exception IllegalArgumentException if this is not an existing property | |
456 | * name for our DynaClass and the MutableDynaClass is restricted | |
457 | * @exception ConversionException if the specified value cannot be | |
458 | * converted to the type required for this property | |
459 | * @exception NullPointerException if an attempt is made to set a | |
460 | * primitive property to null | |
461 | */ | |
462 | public void set(String name, Object value) { | |
463 | ||
464 | // If the property doesn't exist, then add it | |
465 | 33 | if (!isDynaProperty(name)) { |
466 | ||
467 | 14 | if (dynaClass.isRestricted()) { |
468 | 3 | throw new IllegalArgumentException |
469 | ("Invalid property name '" + name + "' (DynaClass is restricted)"); | |
470 | } | |
471 | 11 | if (value == null) { |
472 | 1 | dynaClass.add(name); |
473 | } else { | |
474 | 10 | dynaClass.add(name, value.getClass()); |
475 | } | |
476 | ||
477 | } | |
478 | ||
479 | 30 | DynaProperty descriptor = dynaClass.getDynaProperty(name); |
480 | ||
481 | 30 | if (value == null) { |
482 | 1 | if (descriptor.getType().isPrimitive()) { |
483 | 0 | throw new NullPointerException |
484 | ("Primitive value for '" + name + "'"); | |
485 | } | |
486 | 29 | } else if (!isAssignable(descriptor.getType(), value.getClass())) { |
487 | 1 | throw new ConversionException |
488 | ("Cannot assign value of type '" + | |
489 | value.getClass().getName() + | |
490 | "' to property '" + name + "' of type '" + | |
491 | descriptor.getType().getName() + "'"); | |
492 | } | |
493 | ||
494 | // Set the property's value | |
495 | 29 | values.put(name, value); |
496 | ||
497 | 29 | } |
498 | ||
499 | /** | |
500 | * Set the value of an indexed property with the specified name. | |
501 | * | |
502 | * @param name Name of the property whose value is to be set | |
503 | * @param index Index of the property to be set | |
504 | * @param value Value to which this property is to be set | |
505 | * | |
506 | * @exception ConversionException if the specified value cannot be | |
507 | * converted to the type required for this property | |
508 | * @exception IllegalArgumentException if there is no property | |
509 | * of the specified name | |
510 | * @exception IllegalArgumentException if the specified property | |
511 | * exists, but is not indexed | |
512 | * @exception IndexOutOfBoundsException if the specified index | |
513 | * is outside the range of the underlying property | |
514 | */ | |
515 | public void set(String name, int index, Object value) { | |
516 | ||
517 | // If its not a property, then create default indexed property | |
518 | 26 | if (!isDynaProperty(name)) { |
519 | 5 | set(name, defaultIndexedProperty(name)); |
520 | } | |
521 | ||
522 | // Get the indexed property | |
523 | 24 | Object indexedProperty = get(name); |
524 | ||
525 | // Check that the property is indexed | |
526 | 24 | if (!dynaClass.getDynaProperty(name).isIndexed()) { |
527 | 2 | throw new IllegalArgumentException |
528 | ("Non-indexed property for '" + name + "[" + index + "]'" | |
529 | + dynaClass.getDynaProperty(name).getType().getName()); | |
530 | } | |
531 | ||
532 | // Grow indexed property to appropriate size | |
533 | 22 | indexedProperty = growIndexedProperty(name, indexedProperty, index); |
534 | ||
535 | // Set the value in an array | |
536 | 22 | if (indexedProperty.getClass().isArray()) { |
537 | 8 | Array.set(indexedProperty, index, value); |
538 | 14 | } else if (indexedProperty instanceof List) { |
539 | 14 | ((List)indexedProperty).set(index, value); |
540 | } else { | |
541 | 0 | throw new IllegalArgumentException |
542 | ("Non-indexed property for '" + name + "[" + index + "]' " | |
543 | + indexedProperty.getClass().getName()); | |
544 | } | |
545 | ||
546 | 22 | } |
547 | ||
548 | /** | |
549 | * Set the value of a mapped property with the specified name. | |
550 | * | |
551 | * @param name Name of the property whose value is to be set | |
552 | * @param key Key of the property to be set | |
553 | * @param value Value to which this property is to be set | |
554 | * | |
555 | * @exception ConversionException if the specified value cannot be | |
556 | * converted to the type required for this property | |
557 | * @exception IllegalArgumentException if there is no property | |
558 | * of the specified name | |
559 | * @exception IllegalArgumentException if the specified property | |
560 | * exists, but is not mapped | |
561 | */ | |
562 | public void set(String name, String key, Object value) { | |
563 | ||
564 | // If the 'mapped' property doesn't exist, then add it | |
565 | 14 | if (!isDynaProperty(name)) { |
566 | 2 | set(name, defaultMappedProperty(name)); |
567 | } | |
568 | ||
569 | // Get the mapped property | |
570 | 12 | Object mappedProperty = get(name); |
571 | ||
572 | // Check that the property is mapped | |
573 | 12 | if (!dynaClass.getDynaProperty(name).isMapped()) { |
574 | 2 | throw new IllegalArgumentException |
575 | ("Non-mapped property for '" + name + "(" + key + ")'" | |
576 | + dynaClass.getDynaProperty(name).getType().getName()); | |
577 | } | |
578 | ||
579 | // Set the value in the Map | |
580 | 10 | ((Map)mappedProperty).put(key, value); |
581 | ||
582 | 10 | } |
583 | ||
584 | // ------------------- protected Methods ---------------------------------- | |
585 | ||
586 | /** | |
587 | * Grow the size of an indexed property | |
588 | * @param name The name of the property | |
589 | * @param indexedProperty The current property value | |
590 | * @param index The indexed value to grow the property to (i.e. one less than | |
591 | * the required size) | |
592 | * @return The new property value (grown to the appropriate size) | |
593 | */ | |
594 | protected Object growIndexedProperty(String name, Object indexedProperty, int index) { | |
595 | ||
596 | // Grow a List to the appropriate size | |
597 | 54 | if (indexedProperty instanceof List) { |
598 | ||
599 | 30 | List list = (List)indexedProperty; |
600 | 72 | while (index >= list.size()) { |
601 | 42 | Class contentType = getDynaClass().getDynaProperty(name).getContentType(); |
602 | 42 | Object value = null; |
603 | 42 | if (contentType != null) { |
604 | 0 | value = createProperty(name+"["+list.size()+"]", contentType); |
605 | } | |
606 | 42 | list.add(value); |
607 | 42 | } |
608 | ||
609 | } | |
610 | ||
611 | // Grow an Array to the appropriate size | |
612 | 54 | if ((indexedProperty.getClass().isArray())) { |
613 | ||
614 | 24 | int length = Array.getLength(indexedProperty); |
615 | 24 | if (index >= length) { |
616 | 10 | Class componentType = indexedProperty.getClass().getComponentType(); |
617 | 10 | Object newArray = Array.newInstance(componentType, (index + 1)); |
618 | 10 | System.arraycopy(indexedProperty, 0, newArray, 0, length); |
619 | 10 | indexedProperty = newArray; |
620 | 10 | set(name, indexedProperty); |
621 | 10 | int newLength = Array.getLength(indexedProperty); |
622 | 42 | for (int i = length; i < newLength; i++) { |
623 | 32 | Array.set(indexedProperty, i, createProperty(name+"["+i+"]", componentType)); |
624 | } | |
625 | } | |
626 | } | |
627 | ||
628 | 54 | return indexedProperty; |
629 | ||
630 | } | |
631 | ||
632 | /** | |
633 | * Create a new Instance of a Property | |
634 | * @param name The name of the property | |
635 | * @param type The class of the property | |
636 | * @return The new value | |
637 | */ | |
638 | protected Object createProperty(String name, Class type) { | |
639 | 48 | if (type == null) { |
640 | 0 | return null; |
641 | } | |
642 | ||
643 | // Create Lists, arrays or DynaBeans | |
644 | 48 | if (type.isArray() || List.class.isAssignableFrom(type)) { |
645 | 10 | return createIndexedProperty(name, type); |
646 | } | |
647 | ||
648 | 38 | if (Map.class.isAssignableFrom(type)) { |
649 | 2 | return createMappedProperty(name, type); |
650 | } | |
651 | ||
652 | 36 | if (DynaBean.class.isAssignableFrom(type)) { |
653 | 8 | return createDynaBeanProperty(name, type); |
654 | } | |
655 | ||
656 | 28 | if (type.isPrimitive()) { |
657 | 12 | return createPrimitiveProperty(name, type); |
658 | } | |
659 | ||
660 | 16 | if (Number.class.isAssignableFrom(type)) { |
661 | 0 | return createNumberProperty(name, type); |
662 | } | |
663 | ||
664 | 16 | return createOtherProperty(name, type); |
665 | ||
666 | } | |
667 | ||
668 | /** | |
669 | * Create a new Instance of an 'Indexed' Property | |
670 | * @param name The name of the property | |
671 | * @param type The class of the property | |
672 | * @return The new value | |
673 | */ | |
674 | protected Object createIndexedProperty(String name, Class type) { | |
675 | ||
676 | // Create the indexed object | |
677 | 10 | Object indexedProperty = null; |
678 | ||
679 | 10 | if (type == null) { |
680 | ||
681 | 0 | indexedProperty = defaultIndexedProperty(name); |
682 | ||
683 | 10 | } else if (type.isArray()) { |
684 | ||
685 | 7 | indexedProperty = Array.newInstance(type.getComponentType(), 0); |
686 | ||
687 | 3 | } else if (List.class.isAssignableFrom(type)) { |
688 | 3 | if (type.isInterface()) { |
689 | 0 | indexedProperty = defaultIndexedProperty(name); |
690 | } else { | |
691 | try { | |
692 | 3 | indexedProperty = type.newInstance(); |
693 | } | |
694 | 0 | catch (Exception ex) { |
695 | 0 | throw new IllegalArgumentException |
696 | ("Error instantiating indexed property of type '" + | |
697 | type.getName() + "' for '" + name + "' " + ex); | |
698 | 3 | } |
699 | } | |
700 | } else { | |
701 | ||
702 | 0 | throw new IllegalArgumentException |
703 | ("Non-indexed property of type '" + type.getName() + "' for '" + name + "'"); | |
704 | } | |
705 | ||
706 | 10 | return indexedProperty; |
707 | ||
708 | } | |
709 | ||
710 | /** | |
711 | * Create a new Instance of a 'Mapped' Property | |
712 | * @param name The name of the property | |
713 | * @param type The class of the property | |
714 | * @return The new value | |
715 | */ | |
716 | protected Object createMappedProperty(String name, Class type) { | |
717 | ||
718 | // Create the mapped object | |
719 | 2 | Object mappedProperty = null; |
720 | ||
721 | 2 | if (type == null) { |
722 | ||
723 | 0 | mappedProperty = defaultMappedProperty(name); |
724 | ||
725 | 2 | } else if (type.isInterface()) { |
726 | ||
727 | 0 | mappedProperty = defaultMappedProperty(name); |
728 | ||
729 | 2 | } else if (Map.class.isAssignableFrom(type)) { |
730 | try { | |
731 | 2 | mappedProperty = type.newInstance(); |
732 | } | |
733 | 0 | catch (Exception ex) { |
734 | 0 | throw new IllegalArgumentException |
735 | ("Error instantiating mapped property of type '" + | |
736 | type.getName() + "' for '" + name + "' " + ex); | |
737 | 2 | } |
738 | } else { | |
739 | ||
740 | 0 | throw new IllegalArgumentException |
741 | ("Non-mapped property of type '" + type.getName() + "' for '" + name + "'"); | |
742 | } | |
743 | ||
744 | 2 | return mappedProperty; |
745 | ||
746 | } | |
747 | ||
748 | /** | |
749 | * Create a new Instance of a 'DynaBean' Property. | |
750 | * @param name The name of the property | |
751 | * @param type The class of the property | |
752 | * @return The new value | |
753 | */ | |
754 | protected Object createDynaBeanProperty(String name, Class type) { | |
755 | try { | |
756 | 8 | return type.newInstance(); |
757 | } | |
758 | 0 | catch (Exception ex) { |
759 | 0 | if (logger().isWarnEnabled()) { |
760 | 0 | logger().warn("Error instantiating DynaBean property of type '" + |
761 | type.getName() + "' for '" + name + "' " + ex); | |
762 | } | |
763 | 0 | return null; |
764 | } | |
765 | } | |
766 | ||
767 | /** | |
768 | * Create a new Instance of a 'Primitive' Property. | |
769 | * @param name The name of the property | |
770 | * @param type The class of the property | |
771 | * @return The new value | |
772 | */ | |
773 | protected Object createPrimitiveProperty(String name, Class type) { | |
774 | ||
775 | 12 | if (type == Boolean.TYPE) { |
776 | 0 | return Boolean.FALSE; |
777 | 12 | } else if (type == Integer.TYPE) { |
778 | 12 | return Integer_ZERO; |
779 | 0 | } else if (type == Long.TYPE) { |
780 | 0 | return Long_ZERO; |
781 | 0 | } else if (type == Double.TYPE) { |
782 | 0 | return Double_ZERO; |
783 | 0 | } else if (type == Float.TYPE) { |
784 | 0 | return Float_ZERO; |
785 | 0 | } else if (type == Byte.TYPE) { |
786 | 0 | return Byte_ZERO; |
787 | 0 | } else if (type == Short.TYPE) { |
788 | 0 | return Short_ZERO; |
789 | 0 | } else if (type == Character.TYPE) { |
790 | 0 | return Character_SPACE; |
791 | } else { | |
792 | 0 | return null; |
793 | } | |
794 | ||
795 | } | |
796 | ||
797 | /** | |
798 | * Create a new Instance of a <code>java.lang.Number</code> Property. | |
799 | * @param name The name of the property | |
800 | * @param type The class of the property | |
801 | * @return The new value | |
802 | */ | |
803 | protected Object createNumberProperty(String name, Class type) { | |
804 | ||
805 | 0 | return null; |
806 | ||
807 | } | |
808 | ||
809 | /** | |
810 | * Create a new Instance of other Property types | |
811 | * @param name The name of the property | |
812 | * @param type The class of the property | |
813 | * @return The new value | |
814 | */ | |
815 | protected Object createOtherProperty(String name, Class type) { | |
816 | ||
817 | 16 | if (type == Object.class || |
818 | type == String.class || | |
819 | type == Boolean.class || | |
820 | type == Character.class || | |
821 | Date.class.isAssignableFrom(type)) { | |
822 | ||
823 | 16 | return null; |
824 | ||
825 | } | |
826 | ||
827 | try { | |
828 | 0 | return type.newInstance(); |
829 | } | |
830 | 0 | catch (Exception ex) { |
831 | 0 | if (logger().isWarnEnabled()) { |
832 | 0 | logger().warn("Error instantiating property of type '" + type.getName() + "' for '" + name + "' " + ex); |
833 | } | |
834 | 0 | return null; |
835 | } | |
836 | } | |
837 | ||
838 | /** | |
839 | * <p>Creates a new <code>ArrayList</code> for an 'indexed' property | |
840 | * which doesn't exist.</p> | |
841 | * | |
842 | * <p>This method shouls be overriden if an alternative <code>List</code> | |
843 | * or <code>Array</code> implementation is required for 'indexed' properties.</p> | |
844 | * | |
845 | * @param name Name of the 'indexed property. | |
846 | * @return The default value for an indexed property (java.util.ArrayList) | |
847 | */ | |
848 | protected Object defaultIndexedProperty(String name) { | |
849 | 9 | return new ArrayList(); |
850 | } | |
851 | ||
852 | /** | |
853 | * <p>Creates a new <code>HashMap</code> for a 'mapped' property | |
854 | * which doesn't exist.</p> | |
855 | * | |
856 | * <p>This method can be overriden if an alternative <code>Map</code> | |
857 | * implementation is required for 'mapped' properties.</p> | |
858 | * | |
859 | * @param name Name of the 'mapped property. | |
860 | * @return The default value for a mapped property (java.util.HashMap) | |
861 | */ | |
862 | protected Map defaultMappedProperty(String name) { | |
863 | 6 | return new HashMap(); |
864 | } | |
865 | ||
866 | /** | |
867 | * Indicates if there is a property with the specified name. | |
868 | * @param name The name of the property to check | |
869 | * @return <code>true<code> if there is a property of the | |
870 | * specified name, otherwise <code>false</code> | |
871 | */ | |
872 | protected boolean isDynaProperty(String name) { | |
873 | ||
874 | 98 | if (name == null) { |
875 | 0 | throw new IllegalArgumentException("No property name specified"); |
876 | } | |
877 | ||
878 | // Handle LazyDynaClasses | |
879 | 98 | if (dynaClass instanceof LazyDynaClass) { |
880 | 98 | return ((LazyDynaClass)dynaClass).isDynaProperty(name); |
881 | } | |
882 | ||
883 | // Handle other MutableDynaClass | |
884 | 0 | return dynaClass.getDynaProperty(name) == null ? false : true; |
885 | ||
886 | } | |
887 | ||
888 | /** | |
889 | * Is an object of the source class assignable to the destination class? | |
890 | * | |
891 | * @param dest Destination class | |
892 | * @param source Source class | |
893 | * @return <code>true<code> if the source class is assignable to the | |
894 | * destination class, otherwise <code>false</code> | |
895 | */ | |
896 | protected boolean isAssignable(Class dest, Class source) { | |
897 | ||
898 | 29 | if (dest.isAssignableFrom(source) || |
899 | ((dest == Boolean.TYPE) && (source == Boolean.class)) || | |
900 | ((dest == Byte.TYPE) && (source == Byte.class)) || | |
901 | ((dest == Character.TYPE) && (source == Character.class)) || | |
902 | ((dest == Double.TYPE) && (source == Double.class)) || | |
903 | ((dest == Float.TYPE) && (source == Float.class)) || | |
904 | ((dest == Integer.TYPE) && (source == Integer.class)) || | |
905 | ((dest == Long.TYPE) && (source == Long.class)) || | |
906 | ((dest == Short.TYPE) && (source == Short.class))) { | |
907 | 28 | return (true); |
908 | } else { | |
909 | 1 | return (false); |
910 | } | |
911 | ||
912 | } | |
913 | ||
914 | /** | |
915 | * <p>Creates a new instance of the <code>Map</code>.</p> | |
916 | * @return a new Map instance | |
917 | */ | |
918 | protected Map newMap() { | |
919 | 114 | return new HashMap(); |
920 | } | |
921 | ||
922 | /** | |
923 | * <p>Returns the <code>Log</code>. | |
924 | */ | |
925 | private Log logger() { | |
926 | 0 | if (logger == null) { |
927 | 0 | logger = LogFactory.getLog(LazyDynaBean.class); |
928 | } | |
929 | 0 | return logger; |
930 | } | |
931 | ||
932 | } |