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 }