    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: 926529 $ $Date: 2010-03-23 11:44:24 +0000 (Tue, 23 Mar 2010) $
    161     * @since 1.8.0
    162     */
    163    public class LazyDynaList extends ArrayList {
    164
    165        /**
    166         * The DynaClass of the List's elements.
    167         */
    168        private DynaClass elementDynaClass;
    169
    170        /**
    171         * The WrapDynaClass if the List's contains
    172         * POJO Bean elements.
    173         *
    174         * N.B. WrapDynaClass isn't serlializable, which
    175         *      is why its stored separately in a
    176         *      transient instance variable.
    177         */
    178        private transient WrapDynaClass wrapDynaClass;
    179
    180        /**
    181         * The type of the List's elements.
    182         */
    183        private Class elementType;
    184
    185        /**
    186         * The DynaBean type of the List's elements.
    187         */
    188        private Class elementDynaBeanType;
    189
    190
    191        // ------------------- Constructors ------------------------------
    192
    193        /**
    194         * Default Constructor.
    195         */
    196        public LazyDynaList() {
    197            super();
    198        }
    199
    200        /**
    201         * Construct a LazyDynaList with the
    202         * specified capacity.
    203         *
    204         * @param capacity The initial capacity of the list.
    205         */
    206        public LazyDynaList(int capacity) {
    207            super(capacity);
    208
    209        }
    210
    211        /**
    212         * Construct a  LazyDynaList with a
    213         * specified DynaClass for its elements.
    214         *
    215         * @param elementDynaClass The DynaClass of the List's elements.
    216         */
    217        public LazyDynaList(DynaClass elementDynaClass) {
    218            super();
    219            setElementDynaClass(elementDynaClass);
    220        }
    221
    222        /**
    223         * Construct a  LazyDynaList with a
    224         * specified type for its elements.
    225         *
    226         * @param elementType The Type of the List's elements.
    227         */
    228        public LazyDynaList(Class elementType) {
    229            super();
    230            setElementType(elementType);
    231        }
    232
    233        /**
    234         * Construct a  LazyDynaList populated with the
    235         * elements of a Collection.
    236         *
    237         * @param collection The Collection to poulate the List from.
    238         */
    239        public LazyDynaList(Collection collection) {
    240            super(collection.size());
    241            addAll(collection);
    242        }
    243
    244        /**
    245         * Construct a  LazyDynaList populated with the
    246         * elements of an Array.
    247         *
    248         * @param array The Array to poulate the List from.
    249         */
    250        public LazyDynaList(Object[] array) {
    251            super(array.length);
    252            for (int i = 0; i < array.length; i++) {
    253                add(array[i]);
    254            }
    255        }
    256
    257
    258        // ------------------- java.util.List Methods --------------------
    259
    260        /**
    261         * <p>Insert an element at the specified index position.</p>
    262         *
    263         * <p>If the index position is greater than the current
    264         *    size of the List, then the List is automatically
    265         *    <i>grown</i> to the appropriate size.</p>
    266         *
    267         * @param index The index position to insert the new element.
    268         * @param element The new element to add.
    269         */
    270        public void add(int index, Object element) {
    271
    272            DynaBean dynaBean = transform(element);
    273
    274            growList(index);
    275
    276            super.add(index, dynaBean);
    277
    278        }
    279
    280        /**
    281         * <p>Add an element to the List.</p>
    282         *
    283         * @param element The new element to add.
    284         * @return true.
    285         */
    286        public boolean add(Object element) {
    287
    288            DynaBean dynaBean = transform(element);
    289
    290            return super.add(dynaBean);
    291
    292        }
    293
    294        /**
    295         * <p>Add all the elements from a Collection to the list.
    296         *
    297         * @param collection The Collection of new elements.
    298         * @return true if elements were added.
    299         */
    300        public boolean addAll(Collection collection) {
    301
    302            if (collection == null || collection.size() == 0) {
    303                return false;
    304            }
    305
    306            ensureCapacity(size() + collection.size());
    307
    308            Iterator iterator = collection.iterator();
    309            while (iterator.hasNext()) {
    310                add(iterator.next());
    311            }
    312
    313            return true;
    314
    315        }
    316
    317        /**
    318         * <p>Insert all the elements from a Collection into the
    319         *    list at a specified position.
    320         *
    321         * <p>If the index position is greater than the current
    322         *    size of the List, then the List is automatically
    323         *    <i>grown</i> to the appropriate size.</p>
    324         *
    325         * @param collection The Collection of new elements.
    326         * @param index The index position to insert the new elements at.
    327         * @return true if elements were added.
    328         */
    329        public boolean addAll(int index, Collection collection) {
    330
    331            if (collection == null || collection.size() == 0) {
    332                return false;
    333            }
    334
    335            ensureCapacity((index > size() ? index : size()) + collection.size());
    336
    337            // Call "tranform" with first element, before
    338            // List is "grown" to ensure the correct DynaClass
    339            // is set.
    340            if (size() == 0) {
    341                transform(collection.iterator().next());
    342            }
    343
    344            growList(index);
    345
    346            Iterator iterator = collection.iterator();
    347            while (iterator.hasNext()) {
    348                add(index++, iterator.next());
    349            }
    350
    351            return true;
    352
    353        }
    354
    355        /**
    356         * <p>Return the element at the specified position.</p>
    357         *
    358         * <p>If the position requested is greater than the current
    359         *    size of the List, then the List is automatically
    360         *    <i>grown</i> (and populated) to the appropriate size.</p>
    361         *
    362         * @param index The index position to insert the new elements at.
    363         * @return The element at the specified position.
    364         */
    365        public Object get(int index) {
    366
    367            growList(index + 1);
    368
    369            return super.get(index);
    370
    371        }
    372
    373        /**
    374         * <p>Set the element at the specified position.</p>
    375         *
    376         * <p>If the position requested is greater than the current
    377         *    size of the List, then the List is automatically
    378         *    <i>grown</i> (and populated) to the appropriate size.</p>
    379         *
    380         * @param index The index position to insert the new element at.
    381         * @param element The new element.
    382         * @return The new element.
    383         */
    384        public Object set(int index, Object element) {
    385
    386            DynaBean dynaBean = transform(element);
    387
    388            growList(index + 1);
    389
    390            return super.set(index, dynaBean);
    391
    392        }
    393
    394        /**
    395         * <p>Converts the List to an Array.</p>
    396         *
    397         * <p>The type of Array created depends on the contents
    398         *    of the List:</p>
    399         * <ul>
    400         *    <li>If the List contains only LazyDynaMap type elements
    401         *        then a java.util.Map[] array will be created.</li>
    402         *    <li>If the List contains only elements which are
    403         *        "wrapped" DynaBeans then an Object[] of the most
    404         *        suitable type will be created.</li>
    405         *    <li>...otherwise a DynaBean[] will be created.</li>
    406         *
    407         * @return An Array of the elements in this List.
    408         */
    409        public Object[] toArray() {
    410
    411            if (size() == 0 && elementType == null) {
    412                return new LazyDynaBean[0];
    413            }
    414
    415            Object[] array = (Object[])Array.newInstance(elementType, size());
    416            for (int i = 0; i < size(); i++) {
    417                if (Map.class.isAssignableFrom(elementType)) {
    418                    array[i] = ((LazyDynaMap)get(i)).getMap();
    419                } else if (DynaBean.class.isAssignableFrom(elementType)) {
    420                    array[i] = get(i);
    421                } else {
    422                    array[i] = ((WrapDynaBean)get(i)).getInstance();
    423                }
    424            }
    425            return array;
    426
    427        }
    428
    429        /**
    430         * <p>Converts the List to an Array of the specified type.</p>
    431         *
    432         * @param model The model for the type of array to return
    433         * @return An Array of the elements in this List.
    434         */
    435        public Object[] toArray(Object[] model) {
    436
    437            // Allocate the Array
    438            Class arrayType = model.getClass().getComponentType();
    439            Object[] array = (Object[])Array.newInstance(arrayType, size());
    440
    441            if (size() == 0 && elementType == null) {
    442                return new LazyDynaBean[0];
    443            }
    444
    445            if ((DynaBean.class.isAssignableFrom(arrayType))) {
    446                for (int i = 0; i < size(); i++) {
    447                    array[i] = get(i);
    448                }
    449                return array;
    450            }
    451
    452            if ((arrayType.isAssignableFrom(elementType))) {
    453                for (int i = 0; i < size(); i++) {
    454                    if (Map.class.isAssignableFrom(elementType)) {
    455                        array[i] = ((LazyDynaMap)get(i)).getMap();
    456                    } else if (DynaBean.class.isAssignableFrom(elementType)) {
    457                        array[i] = get(i);
    458                    } else {
    459                        array[i] = ((WrapDynaBean)get(i)).getInstance();
    460                    }
    461                }
    462                return array;
    463            }
    464
    465            throw new IllegalArgumentException("Invalid array type: "
    466                      + arrayType.getName() + " - not compatible with '"
    467                      + elementType.getName());
    468
    469        }
    470
    471
    472        // ------------------- Public Methods ----------------------------
    473
    474        /**
    475         * <p>Converts the List to an DynaBean Array.</p>
    476         *
    477         * @return A DynaBean[] of the elements in this List.
    478         */
    479        public DynaBean[] toDynaBeanArray() {
    480
    481            if (size() == 0 && elementDynaBeanType == null) {
    482                return new LazyDynaBean[0];
    483            }
    484
    485            DynaBean[] array = (DynaBean[])Array.newInstance(elementDynaBeanType, size());
    486            for (int i = 0; i < size(); i++) {
    487                array[i] = (DynaBean)get(i);
    488            }
    489            return array;
    490
    491        }
    492
    493        /**
    494         * <p>Set the element Type and DynaClass.</p>
    495         *
    496         * @param elementType The type of the elements.
    497         * @exception IllegalArgumentException if the List already
    498         *            contains elements or the DynaClass is null.
    499         */
    500        public void setElementType(Class elementType) {
    501
    502            if (elementType == null) {
    503                throw new IllegalArgumentException("Element Type is missing");
    504            }
    505
    506            boolean changeType = (this.elementType != null && !this.elementType.equals(elementType));
    507            if (changeType && size() > 0) {
    508                throw new IllegalStateException("Element Type cannot be reset");
    509            }
    510
    511            this.elementType = elementType;
    512
    513            // Create a new object of the specified type
    514            Object object = null;
    515            try {
    516                object = elementType.newInstance();
    517            } catch (Exception e) {
    518                throw new IllegalArgumentException("Error creating type: "
    519                               + elementType.getName() + " - " + e);
    520            }
    521
    522            // Create a DynaBean
    523            DynaBean dynaBean = null;
    524            if (Map.class.isAssignableFrom(elementType)) {
    525                dynaBean = new LazyDynaMap((Map)object);
    526                this.elementDynaClass = dynaBean.getDynaClass();
    527            } else if (DynaBean.class.isAssignableFrom(elementType)) {
    528                dynaBean = (DynaBean)object;
    529                this.elementDynaClass = dynaBean.getDynaClass();
    530            } else {
    531                dynaBean = new WrapDynaBean(object);
    532                this.wrapDynaClass = (WrapDynaClass)dynaBean.getDynaClass();
    533            }
    534
    535            this.elementDynaBeanType = dynaBean.getClass();
    536
    537            // Re-calculate the type
    538            if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType )) {
    539                this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
    540            } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType )) {
    541                this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass();
    542            }
    543
    544        }
    545
    546        /**
    547         * <p>Set the element Type and DynaClass.</p>
    548         *
    549         * @param elementDynaClass The DynaClass of the elements.
    550         * @exception IllegalArgumentException if the List already
    551         *            contains elements or the DynaClass is null.
    552         */
    553        public void setElementDynaClass(DynaClass elementDynaClass) {
    554
    555            if (elementDynaClass == null) {
    556                throw new IllegalArgumentException("Element DynaClass is missing");
    557            }
    558
    559            if (size() > 0) {
    560                throw new IllegalStateException("Element DynaClass cannot be reset");
    561            }
    562
    563            // Try to create a new instance of the DynaBean
    564            try {
    565                DynaBean dynaBean  = elementDynaClass.newInstance();
    566                this.elementDynaBeanType = dynaBean.getClass();
    567                if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType)) {
    568                    this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
    569                    this.wrapDynaClass = (WrapDynaClass)elementDynaClass;
    570                } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType)) {
    571                    this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass();
    572                    this.elementDynaClass = elementDynaClass;
    573                } else {
    574                    this.elementType = dynaBean.getClass();
    575                    this.elementDynaClass = elementDynaClass;
    576                }
    577            } catch (Exception e) {
    578                throw new IllegalArgumentException(
    579                            "Error creating DynaBean from " +
    580                            elementDynaClass.getClass().getName() + " - " + e);
    581            }
    582
    583        }
    584
    585
    586        // ------------------- Private Methods ---------------------------
    587
    588        /**
    589         * <p>Automatically <i>grown</i> the List
    590         *    to the appropriate size, populating with
    591         *    DynaBeans.</p>
    592         *
    593         * @param requiredSize the required size of the List.
    594         */
    595        private void growList(int requiredSize) {
    596
    597            if (requiredSize < size()) {
    598                return;
    599            }
    600
    601            ensureCapacity(requiredSize + 1);
    602
    603            for (int i = size(); i < requiredSize; i++) {
    604                DynaBean dynaBean = transform(null);
    605                super.add(dynaBean);
    606            }
    607
    608        }
    609
    610        /**
    611         * <p>Transform the element into a DynaBean:</p>
    612         *
    613         * <ul>
    614         *    <li>Map elements are turned into LazyDynaMap's.</li>
    615         *    <li>POJO Beans are "wrapped" in a WrapDynaBean.</li>
    616         *    <li>DynaBeans are unchanged.</li>
    617         * </li>
    618         *
    619         * @param element The element to transformt.
    620         * @param The DynaBean to store in the List.
    621         */
    622        private DynaBean transform(Object element) {
    623
    624            DynaBean dynaBean     = null;
    625            Class newDynaBeanType = null;
    626            Class newElementType  = null;
    627
    628            // Create a new element
    629            if (element == null) {
    630
    631                // Default Types to LazyDynaBean
    632                // if not specified
    633                if (elementType == null) {
    634                    setElementDynaClass(new LazyDynaClass());
    635                }
    636
    637                // Get DynaClass (restore WrapDynaClass lost in serialization)
    638                if (getDynaClass() == null) {
    639                    setElementType(elementType);
    640                }
    641
    642                // Create a new DynaBean
    643                try {
    644                    dynaBean = getDynaClass().newInstance();
    645                    newDynaBeanType = dynaBean.getClass();
    646                } catch (Exception e) {
    647                    throw new IllegalArgumentException("Error creating DynaBean: "
    648                                  + getDynaClass().getClass().getName()
    649                                  + " - " + e);
    650                }
    651
    652            } else {
    653
    654                // Transform Object to a DynaBean
    655                newElementType = element.getClass();
    656                if (Map.class.isAssignableFrom(element.getClass())) {
    657                    dynaBean = new LazyDynaMap((Map)element);
    658                } else if (DynaBean.class.isAssignableFrom(element.getClass())) {
    659                    dynaBean = (DynaBean)element;
    660                } else {
    661                    dynaBean = new WrapDynaBean(element);
    662                }
    663
    664                newDynaBeanType = dynaBean.getClass();
    665
    666            }
    667
    668            // Re-calculate the element type
    669            newElementType = dynaBean.getClass();
    670            if (WrapDynaBean.class.isAssignableFrom(newDynaBeanType)) {
    671                newElementType = ((WrapDynaBean)dynaBean).getInstance().getClass();
    672            } else if (LazyDynaMap.class.isAssignableFrom(newDynaBeanType)) {
    673                newElementType = ((LazyDynaMap)dynaBean).getMap().getClass();
    674            }
    675
    676            // Check the new element type, matches all the
    677            // other elements in the List
    678            if (elementType != null && !newElementType.equals(elementType)) {
    679                throw new IllegalArgumentException("Element Type "  + newElementType
    680                           + " doesn't match other elements " + elementType);
    681            }
    682
    683            return dynaBean;
    684
    685        }
    686
    687        /**
    688         * Return the DynaClass.
    689         */
    690        private DynaClass getDynaClass() {
    691            return (elementDynaClass == null ? wrapDynaClass : elementDynaClass);
    692        }
    693    }