001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.beanutils;
018    
019    import java.util.ArrayList;
020    import java.util.Map;
021    import java.util.Collection;
022    import java.util.Iterator;
023    import java.lang.reflect.Array;
024    
025    /**
026     * <h2><i>Lazy</i> DynaBean List.</h2>
027     * 
028     * <p>There are two main purposes for this class:</p>
029     *    <ul>
030     *        <li>To provide <i>Lazy List</i> behaviour - automatically
031     *            <i>growing</i> and <i>populating</i> the <code>List</code>
032     *            with either <code>DynaBean</code>, <code>java.util.Map</code>
033     *            or POJO Beans.</li>
034     *        <li>To provide a straight forward way of putting a Collection
035     *            or Array into the lazy list <i>and</i> a straight forward 
036     *            way to get it out again at the end.</li>
037     *    </ul>
038     * 
039     * <p>All elements added to the List are stored as <code>DynaBean</code>'s:</p>
040     * <ul>
041     *    <li><code>java.util.Map</code> elements are "wrapped" in a <code>LazyDynaMap</code>.</i> 
042     *    <li>POJO Bean elements are "wrapped" in a <code>WrapDynaBean.</code></i> 
043     *    <li><code>DynaBean</code>'s are stored un-changed.</i>
044     * </ul>
045     *  
046     * <h4><code>toArray()</code></h4>
047     * <p>The <code>toArray()</code> method returns an array of the
048     *    elements of the appropriate type. If the <code>LazyDynaList</code>
049     *    is populated with <code>java.util.Map</code> objects a 
050     *    <code>Map[]</code> array is returned.
051     *    If the list is populated with POJO Beans an appropriate
052     *    array of the POJO Beans is returned. Otherwise a <code>DynaBean[]</code>
053     *    array is returned.
054     * </p>
055     *  
056     * <h4><code>toDynaBeanArray()</code></h4>
057     * <p>The <code>toDynaBeanArray()</code> method returns a 
058     *    <code>DynaBean[]</code> array of the elements in the List.
059     * </p>
060     *  
061     * <p><strong>N.B.</strong>All the elements in the List must be the
062     *    same type. If the <code>DynaClass</code> or <code>Class</code>
063     *    of the <code>LazyDynaList</code>'s elements is
064     *    not specified, then it will be automatically set to the type
065     *    of the first element populated.
066     * </p>
067     * 
068     * <h3>Example 1</h3>
069     * <p>If you have an array of <code>java.util.Map[]</code> - you can put that into
070     *    a <code>LazyDynaList</code>.</p>
071     * 
072     * <pre><code>
073     *    TreeMap[] myArray = .... // your Map[]
074     *    List lazyList = new LazyDynaList(myArray);
075     * </code></pre>
076     * 
077     * <p>New elements of the appropriate Map type are
078     *    automatically populated:</p>
079     *  
080     * <pre><code>
081     *    // get(index) automatically grows the list
082     *    DynaBean newElement = (DynaBean)lazyList.get(lazyList.size());
083     *    newElement.put("someProperty", "someValue");
084     * </code></pre>
085     * 
086     * <p>Once you've finished you can get back an Array of the
087     *    elements of the appropriate type:</p>
088     *  
089     * <pre><code>
090     *    // Retrieve the array from the list
091     *    TreeMap[] myArray = (TreeMap[])lazyList.toArray());
092     * </code></pre>
093     * 
094     * 
095     * <h3>Example 2</h3>
096     * <p>Alternatively you can create an <i>empty</i> List and
097     *    specify the Class for List's elements. The LazyDynaList
098     *    uses the Class to automatically populate elements:</p>
099     * 
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    }