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: 800638 $ $Date: 2009-08-04 01:23:41 +0100 (Tue, 04 Aug 2009) $
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 }