View Javadoc

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.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     // ------------------- Constructors ------------------------------
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     // ------------------- java.util.List Methods --------------------
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         // Call "tranform" with first element, before
337         // List is "grown" to ensure the correct DynaClass
338         // is set.
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         // Allocate the Array
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     // ------------------- Public Methods ----------------------------
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         // Create a new object of the specified type
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         // Create a DynaBean
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         // Re-calculate the type
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         // Try to create a new instance of the DynaBean
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     // ------------------- Private Methods ---------------------------
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         // Create a new element
627         if (element == null) {
628 
629             // Default Types to LazyDynaBean
630             // if not specified
631             if (elementType == null) {
632                 setElementDynaClass(new LazyDynaClass());
633             }
634 
635             // Get DynaClass (restore WrapDynaClass lost in serialization)
636             DynaClass dynaClass = (elementDynaClass == null) ? wrapDynaClass : elementDynaClass;
637             if (dynaClass == null) {
638                 setElementType(elementType);
639             }
640                          
641             // Create a new DynaBean            
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             // Transform Object to a DynaBean
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         // Re-calculate the element type
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         // Check the new element type, matches all the 
676         // other elements in the List
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 }