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.ArrayList;
20 import java.util.Map;
21 import java.util.Collection;
22 import java.util.Iterator;
23 import java.lang.reflect.Array;
24
25 /***
26 * <h2><i>Lazy</i> DynaBean List.</h2>
27 *
28 * <p>There are two main purposes for this class:</p>
29 * <ul>
30 * <li>To provide <i>Lazy List</i> behaviour - automatically
31 * <i>growing</i> and <i>populating</i> the <code>List</code>
32 * with either <code>DynaBean</code>, <code>java.util.Map</code>
33 * or POJO Beans.</li>
34 * <li>To provide a straight forward way of putting a Collection
35 * or Array into the lazy list <i>and</i> a straight forward
36 * way to get it out again at the end.</li>
37 * </ul>
38 *
39 * <p>All elements added to the List are stored as <code>DynaBean</code>'s:</p>
40 * <ul>
41 * <li><code>java.util.Map</code> elements are "wrapped" in a <code>LazyDynaMap</code>.</i>
42 * <li>POJO Bean elements are "wrapped" in a <code>WrapDynaBean.</code></i>
43 * <li><code>DynaBean</code>'s are stored un-changed.</i>
44 * </ul>
45 *
46 * <h4><code>toArray()</code></h4>
47 * <p>The <code>toArray()</code> method returns an array of the
48 * elements of the appropriate type. If the <code>LazyDynaList</code>
49 * is populated with <code>java.util.Map</code> objects a
50 * <code>Map[]</code> array is returned.
51 * If the list is populated with POJO Beans an appropriate
52 * array of the POJO Beans is returned. Otherwise a <code>DynaBean[]</code>
53 * array is returned.
54 * </p>
55 *
56 * <h4><code>toDynaBeanArray()</code></h4>
57 * <p>The <code>toDynaBeanArray()</code> method returns a
58 * <code>DynaBean[]</code> array of the elements in the List.
59 * </p>
60 *
61 * <p><strong>N.B.</strong>All the elements in the List must be the
62 * same type. If the <code>DynaClass</code> or <code>Class</code>
63 * of the <code>LazyDynaList</code>'s elements is
64 * not specified, then it will be automatically set to the type
65 * of the first element populated.
66 * </p>
67 *
68 * <h3>Example 1</h3>
69 * <p>If you have an array of <code>java.util.Map[]</code> - you can put that into
70 * a <code>LazyDynaList</code>.</p>
71 *
72 * <pre><code>
73 * TreeMap[] myArray = .... // your Map[]
74 * List lazyList = new LazyDynaList(myArray);
75 * </code></pre>
76 *
77 * <p>New elements of the appropriate Map type are
78 * automatically populated:</p>
79 *
80 * <pre><code>
81 * // get(index) automatically grows the list
82 * DynaBean newElement = (DynaBean)lazyList.get(lazyList.size());
83 * newElement.put("someProperty", "someValue");
84 * </code></pre>
85 *
86 * <p>Once you've finished you can get back an Array of the
87 * elements of the appropriate type:</p>
88 *
89 * <pre><code>
90 * // Retrieve the array from the list
91 * TreeMap[] myArray = (TreeMap[])lazyList.toArray());
92 * </code></pre>
93 *
94 *
95 * <h3>Example 2</h3>
96 * <p>Alternatively you can create an <i>empty</i> List and
97 * specify the Class for List's elements. The LazyDynaList
98 * uses the Class to automatically populate elements:</p>
99 *
100 * <pre><code>
101 * // e.g. For Maps
102 * List lazyList = new LazyDynaList(TreeMap.class);
103 *
104 * // e.g. For POJO Beans
105 * List lazyList = new LazyDynaList(MyPojo.class);
106 *
107 * // e.g. For DynaBeans
108 * List lazyList = new LazyDynaList(MyDynaBean.class);
109 * </code></pre>
110 *
111 * <h3>Example 3</h3>
112 * <p>Alternatively you can create an <i>empty</i> List and specify the
113 * DynaClass for List's elements. The LazyDynaList uses
114 * the DynaClass to automatically populate elements:</p>
115 *
116 * <pre><code>
117 * // e.g. For Maps
118 * DynaClass dynaClass = new LazyDynaMap(new HashMap());
119 * List lazyList = new LazyDynaList(dynaClass);
120 *
121 * // e.g. For POJO Beans
122 * DynaClass dynaClass = (new WrapDynaBean(myPojo)).getDynaClass();
123 * List lazyList = new LazyDynaList(dynaClass);
124 *
125 * // e.g. For DynaBeans
126 * DynaClass dynaClass = new BasicDynaClass(properties);
127 * List lazyList = new LazyDynaList(dynaClass);
128 * </code></pre>
129 *
130 * <p><strong>N.B.</strong> You may wonder why control the type
131 * using a <code>DynaClass</code> rather than the <code>Class</code>
132 * as in the previous example - the reason is that some <code>DynaBean</code>
133 * implementations don't have a <i>default</i> empty constructor and
134 * therefore need to be instantiated using the <code>DynaClass.newInstance()</code>
135 * method.</p>
136 *
137 * <h3>Example 4</h3>
138 * <p>A slight variation - set the element type using either
139 * the <code>setElementType(Class)</code> method or the
140 * <code>setElementDynaClass(DynaClass)</code> method - then populate
141 * with the normal <code>java.util.List</code> methods(i.e.
142 * <code>add()</code>, <code>addAll()</code> or <code>set()</code>).</p>
143 *
144 * <pre><code>
145 * // Create a new LazyDynaList (100 element capacity)
146 * LazyDynaList lazyList = new LazyDynaList(100);
147 *
148 * // Either Set the element type...
149 * lazyList.setElementType(TreeMap.class);
150 *
151 * // ...or the element DynaClass...
152 * lazyList.setElementDynaClass(new MyCustomDynaClass());
153 *
154 * // Populate from a collection
155 * lazyList.addAll(myCollection);
156 *
157 * </code></pre>
158 *
159 * @author Niall Pemberton
160 * @version $Revision: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $
161 */
162 public class LazyDynaList extends ArrayList {
163
164 /***
165 * The DynaClass of the List's elements.
166 */
167 private DynaClass elementDynaClass;
168
169 /***
170 * The WrapDynaClass if the List's contains
171 * POJO Bean elements.
172 *
173 * N.B. WrapDynaClass isn't serlializable, which
174 * is why its stored separately in a
175 * transient instance variable.
176 */
177 private transient WrapDynaClass wrapDynaClass;
178
179 /***
180 * The type of the List's elements.
181 */
182 private Class elementType;
183
184 /***
185 * The DynaBean type of the List's elements.
186 */
187 private Class elementDynaBeanType;
188
189
190
191
192 /***
193 * Default Constructor.
194 */
195 public LazyDynaList() {
196 super();
197 }
198
199 /***
200 * Construct a LazyDynaList with the
201 * specified capacity.
202 *
203 * @param capacity The initial capacity of the list.
204 */
205 public LazyDynaList(int capacity) {
206 super(capacity);
207
208 }
209
210 /***
211 * Construct a LazyDynaList with a
212 * specified DynaClass for its elements.
213 *
214 * @param elementDynaClass The DynaClass of the List's elements.
215 */
216 public LazyDynaList(DynaClass elementDynaClass) {
217 super();
218 setElementDynaClass(elementDynaClass);
219 }
220
221 /***
222 * Construct a LazyDynaList with a
223 * specified type for its elements.
224 *
225 * @param elementType The Type of the List's elements.
226 */
227 public LazyDynaList(Class elementType) {
228 super();
229 setElementType(elementType);
230 }
231
232 /***
233 * Construct a LazyDynaList populated with the
234 * elements of a Collection.
235 *
236 * @param collection The Collection to poulate the List from.
237 */
238 public LazyDynaList(Collection collection) {
239 super(collection.size());
240 addAll(collection);
241 }
242
243 /***
244 * Construct a LazyDynaList populated with the
245 * elements of an Array.
246 *
247 * @param array The Array to poulate the List from.
248 */
249 public LazyDynaList(Object[] array) {
250 super(array.length);
251 for (int i = 0; i < array.length; i++) {
252 add(array[i]);
253 }
254 }
255
256
257
258
259 /***
260 * <p>Insert an element at the specified index position.</p>
261 *
262 * <p>If the index position is greater than the current
263 * size of the List, then the List is automatically
264 * <i>grown</i> to the appropriate size.</p>
265 *
266 * @param index The index position to insert the new element.
267 * @param element The new element to add.
268 */
269 public void add(int index, Object element) {
270
271 DynaBean dynaBean = transform(element);
272
273 growList(index);
274
275 super.add(index, dynaBean);
276
277 }
278
279 /***
280 * <p>Add an element to the List.</p>
281 *
282 * @param element The new element to add.
283 * @return true.
284 */
285 public boolean add(Object element) {
286
287 DynaBean dynaBean = transform(element);
288
289 return super.add(dynaBean);
290
291 }
292
293 /***
294 * <p>Add all the elements from a Collection to the list.
295 *
296 * @param collection The Collection of new elements.
297 * @return true if elements were added.
298 */
299 public boolean addAll(Collection collection) {
300
301 if (collection == null || collection.size() == 0) {
302 return false;
303 }
304
305 ensureCapacity(size() + collection.size());
306
307 Iterator iterator = collection.iterator();
308 while (iterator.hasNext()) {
309 add(iterator.next());
310 }
311
312 return true;
313
314 }
315
316 /***
317 * <p>Insert all the elements from a Collection into the
318 * list at a specified position.
319 *
320 * <p>If the index position is greater than the current
321 * size of the List, then the List is automatically
322 * <i>grown</i> to the appropriate size.</p>
323 *
324 * @param collection The Collection of new elements.
325 * @param index The index position to insert the new elements at.
326 * @return true if elements were added.
327 */
328 public boolean addAll(int index, Collection collection) {
329
330 if (collection == null || collection.size() == 0) {
331 return false;
332 }
333
334 ensureCapacity((index > size() ? index : size()) + collection.size());
335
336
337
338
339 if (size() == 0) {
340 transform(collection.iterator().next());
341 }
342
343 growList(index);
344
345 Iterator iterator = collection.iterator();
346 while (iterator.hasNext()) {
347 add(index++, iterator.next());
348 }
349
350 return true;
351
352 }
353
354 /***
355 * <p>Return the element at the specified position.</p>
356 *
357 * <p>If the position requested is greater than the current
358 * size of the List, then the List is automatically
359 * <i>grown</i> (and populated) to the appropriate size.</p>
360 *
361 * @param index The index position to insert the new elements at.
362 * @return The element at the specified position.
363 */
364 public Object get(int index) {
365
366 growList(index + 1);
367
368 return super.get(index);
369
370 }
371
372 /***
373 * <p>Set the element at the specified position.</p>
374 *
375 * <p>If the position requested is greater than the current
376 * size of the List, then the List is automatically
377 * <i>grown</i> (and populated) to the appropriate size.</p>
378 *
379 * @param index The index position to insert the new element at.
380 * @param element The new element.
381 * @return The new element.
382 */
383 public Object set(int index, Object element) {
384
385 DynaBean dynaBean = transform(element);
386
387 growList(index + 1);
388
389 return super.set(index, dynaBean);
390
391 }
392
393 /***
394 * <p>Converts the List to an Array.</p>
395 *
396 * <p>The type of Array created depends on the contents
397 * of the List:</p>
398 * <ul>
399 * <li>If the List contains only LazyDynaMap type elements
400 * then a java.util.Map[] array will be created.</li>
401 * <li>If the List contains only elements which are
402 * "wrapped" DynaBeans then an Object[] of the most
403 * suitable type will be created.</li>
404 * <li>...otherwise a DynaBean[] will be created.</li>
405 *
406 * @return An Array of the elements in this List.
407 */
408 public Object[] toArray() {
409
410 if (size() == 0 && elementType == null) {
411 return new LazyDynaBean[0];
412 }
413
414 Object[] array = (Object[])Array.newInstance(elementType, size());
415 for (int i = 0; i < size(); i++) {
416 if (Map.class.isAssignableFrom(elementType)) {
417 array[i] = ((LazyDynaMap)get(i)).getMap();
418 } else if (DynaBean.class.isAssignableFrom(elementType)) {
419 array[i] = (DynaBean)get(i);
420 } else {
421 array[i] = ((WrapDynaBean)get(i)).getInstance();
422 }
423 }
424 return array;
425
426 }
427
428 /***
429 * <p>Converts the List to an Array of the specified type.</p>
430 *
431 * @param model The model for the type of array to return
432 * @return An Array of the elements in this List.
433 */
434 public Object[] toArray(Object[] model) {
435
436
437 Class arrayType = model.getClass().getComponentType();
438 Object[] array = (Object[])Array.newInstance(arrayType, size());
439
440 if (size() == 0 && elementType == null) {
441 return new LazyDynaBean[0];
442 }
443
444 if ((DynaBean.class.isAssignableFrom(arrayType))) {
445 for (int i = 0; i < size(); i++) {
446 array[i] = get(i);
447 }
448 return array;
449 }
450
451 if ((arrayType.isAssignableFrom(elementType))) {
452 for (int i = 0; i < size(); i++) {
453 if (Map.class.isAssignableFrom(elementType)) {
454 array[i] = ((LazyDynaMap)get(i)).getMap();
455 } else if (DynaBean.class.isAssignableFrom(elementType)) {
456 array[i] = get(i);
457 } else {
458 array[i] = ((WrapDynaBean)get(i)).getInstance();
459 }
460 }
461 return array;
462 }
463
464 throw new IllegalArgumentException("Invalid array type: "
465 + arrayType.getName() + " - not compatible with '"
466 + elementType.getName());
467
468 }
469
470
471
472
473 /***
474 * <p>Converts the List to an DynaBean Array.</p>
475 *
476 * @return A DynaBean[] of the elements in this List.
477 */
478 public DynaBean[] toDynaBeanArray() {
479
480 if (size() == 0 && elementDynaBeanType == null) {
481 return new LazyDynaBean[0];
482 }
483
484 DynaBean[] array = (DynaBean[])Array.newInstance(elementDynaBeanType, size());
485 for (int i = 0; i < size(); i++) {
486 array[i] = (DynaBean)get(i);
487 }
488 return array;
489
490 }
491
492 /***
493 * <p>Set the element Type and DynaClass.</p>
494 *
495 * @param elementType The type of the elements.
496 * @exception IllegalArgumentException if the List already
497 * contains elements or the DynaClass is null.
498 */
499 public void setElementType(Class elementType) {
500
501 if (elementType == null) {
502 throw new IllegalArgumentException("Element Type is missing");
503 }
504
505 if (size() > 0) {
506 throw new IllegalStateException("Element Type cannot be reset");
507 }
508
509 this.elementType = elementType;
510
511
512 Object object = null;
513 try {
514 object = elementType.newInstance();
515 } catch (Exception e) {
516 throw new IllegalArgumentException("Error creating type: "
517 + elementType.getName() + " - " + e);
518 }
519
520
521 DynaBean dynaBean = null;
522 if (Map.class.isAssignableFrom(elementType)) {
523 dynaBean = new LazyDynaMap((Map)object);
524 this.elementDynaClass = dynaBean.getDynaClass();
525 } else if (DynaBean.class.isAssignableFrom(elementType)) {
526 dynaBean = (DynaBean)object;
527 this.elementDynaClass = dynaBean.getDynaClass();
528 } else {
529 dynaBean = new WrapDynaBean(object);
530 this.wrapDynaClass = (WrapDynaClass)dynaBean.getDynaClass();
531 }
532
533 this.elementDynaBeanType = dynaBean.getClass();
534
535
536 if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType )) {
537 this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
538 } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType )) {
539 this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass();
540 }
541
542 }
543
544 /***
545 * <p>Set the element Type and DynaClass.</p>
546 *
547 * @param elementDynaClass The DynaClass of the elements.
548 * @exception IllegalArgumentException if the List already
549 * contains elements or the DynaClass is null.
550 */
551 public void setElementDynaClass(DynaClass elementDynaClass) {
552
553 if (elementDynaClass == null) {
554 throw new IllegalArgumentException("Element DynaClass is missing");
555 }
556
557 if (size() > 0) {
558 throw new IllegalStateException("Element DynaClass cannot be reset");
559 }
560
561
562 try {
563 DynaBean dynaBean = elementDynaClass.newInstance();
564 this.elementDynaBeanType = dynaBean.getClass();
565 if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType)) {
566 this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
567 this.wrapDynaClass = (WrapDynaClass)elementDynaClass;
568 } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType)) {
569 this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass();
570 this.elementDynaClass = elementDynaClass;
571 } else {
572 this.elementType = dynaBean.getClass();
573 this.elementDynaClass = elementDynaClass;
574 }
575 } catch (Exception e) {
576 throw new IllegalArgumentException(
577 "Error creating DynaBean from " +
578 elementDynaClass.getClass().getName() + " - " + e);
579 }
580
581 }
582
583
584
585
586 /***
587 * <p>Automatically <i>grown</i> the List
588 * to the appropriate size, populating with
589 * DynaBeans.</p>
590 *
591 * @param requiredSize the required size of the List.
592 */
593 private void growList(int requiredSize) {
594
595 if (requiredSize < size()) {
596 return;
597 }
598
599 ensureCapacity(requiredSize + 1);
600
601 for (int i = size(); i < requiredSize; i++) {
602 DynaBean dynaBean = transform(null);
603 super.add(dynaBean);
604 }
605
606 }
607
608 /***
609 * <p>Transform the element into a DynaBean:</p>
610 *
611 * <ul>
612 * <li>Map elements are turned into LazyDynaMap's.</li>
613 * <li>POJO Beans are "wrapped" in a WrapDynaBean.</li>
614 * <li>DynaBeans are unchanged.</li>
615 * </li>
616 *
617 * @param element The element to transformt.
618 * @param The DynaBean to store in the List.
619 */
620 private DynaBean transform(Object element) {
621
622 DynaBean dynaBean = null;
623 Class newDynaBeanType = null;
624 Class newElementType = null;
625
626
627 if (element == null) {
628
629
630
631 if (elementType == null) {
632 setElementDynaClass(new LazyDynaClass());
633 }
634
635
636 DynaClass dynaClass = (elementDynaClass == null) ? wrapDynaClass : elementDynaClass;
637 if (dynaClass == null) {
638 setElementType(elementType);
639 }
640
641
642 try {
643 dynaBean = dynaClass.newInstance();
644 newDynaBeanType = dynaBean.getClass();
645 } catch (Exception e) {
646 throw new IllegalArgumentException("Error creating DynaBean: "
647 + dynaClass.getClass().getName()
648 + " - " + e);
649 }
650
651 } else {
652
653
654 newElementType = element.getClass();
655 if (Map.class.isAssignableFrom(element.getClass())) {
656 dynaBean = new LazyDynaMap((Map)element);
657 } else if (DynaBean.class.isAssignableFrom(element.getClass())) {
658 dynaBean = (DynaBean)element;
659 } else {
660 dynaBean = new WrapDynaBean(element);
661 }
662
663 newDynaBeanType = dynaBean.getClass();
664
665 }
666
667
668 newElementType = dynaBean.getClass();
669 if (WrapDynaBean.class.isAssignableFrom(newDynaBeanType)) {
670 newElementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
671 } else if (LazyDynaMap.class.isAssignableFrom(newDynaBeanType)) {
672 newElementType = ((LazyDynaMap)dynaBean).getMap().getClass();
673 }
674
675
676
677 if (newElementType != elementType) {
678 throw new IllegalArgumentException("Element Type " + newElementType
679 + " doesn't match other elements " + elementType);
680 }
681
682 return dynaBean;
683
684 }
685
686 }