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: // ------------------- Constructors ------------------------------
190:
191: /**
192: * Default Constructor.
193: */
194: public LazyDynaList() {
195: super ();
196: }
197:
198: /**
199: * Construct a LazyDynaList with the
200: * specified capacity.
201: *
202: * @param capacity The initial capacity of the list.
203: */
204: public LazyDynaList(int capacity) {
205: super (capacity);
206:
207: }
208:
209: /**
210: * Construct a LazyDynaList with a
211: * specified DynaClass for its elements.
212: *
213: * @param elementDynaClass The DynaClass of the List's elements.
214: */
215: public LazyDynaList(DynaClass elementDynaClass) {
216: super ();
217: setElementDynaClass(elementDynaClass);
218: }
219:
220: /**
221: * Construct a LazyDynaList with a
222: * specified type for its elements.
223: *
224: * @param elementType The Type of the List's elements.
225: */
226: public LazyDynaList(Class elementType) {
227: super ();
228: setElementType(elementType);
229: }
230:
231: /**
232: * Construct a LazyDynaList populated with the
233: * elements of a Collection.
234: *
235: * @param collection The Collection to poulate the List from.
236: */
237: public LazyDynaList(Collection collection) {
238: super (collection.size());
239: addAll(collection);
240: }
241:
242: /**
243: * Construct a LazyDynaList populated with the
244: * elements of an Array.
245: *
246: * @param array The Array to poulate the List from.
247: */
248: public LazyDynaList(Object[] array) {
249: super (array.length);
250: for (int i = 0; i < array.length; i++) {
251: add(array[i]);
252: }
253: }
254:
255: // ------------------- java.util.List Methods --------------------
256:
257: /**
258: * <p>Insert an element at the specified index position.</p>
259: *
260: * <p>If the index position is greater than the current
261: * size of the List, then the List is automatically
262: * <i>grown</i> to the appropriate size.</p>
263: *
264: * @param index The index position to insert the new element.
265: * @param element The new element to add.
266: */
267: public void add(int index, Object element) {
268:
269: DynaBean dynaBean = transform(element);
270:
271: growList(index);
272:
273: super .add(index, dynaBean);
274:
275: }
276:
277: /**
278: * <p>Add an element to the List.</p>
279: *
280: * @param element The new element to add.
281: * @return true.
282: */
283: public boolean add(Object element) {
284:
285: DynaBean dynaBean = transform(element);
286:
287: return super .add(dynaBean);
288:
289: }
290:
291: /**
292: * <p>Add all the elements from a Collection to the list.
293: *
294: * @param collection The Collection of new elements.
295: * @return true if elements were added.
296: */
297: public boolean addAll(Collection collection) {
298:
299: if (collection == null || collection.size() == 0) {
300: return false;
301: }
302:
303: ensureCapacity(size() + collection.size());
304:
305: Iterator iterator = collection.iterator();
306: while (iterator.hasNext()) {
307: add(iterator.next());
308: }
309:
310: return true;
311:
312: }
313:
314: /**
315: * <p>Insert all the elements from a Collection into the
316: * list at a specified position.
317: *
318: * <p>If the index position is greater than the current
319: * size of the List, then the List is automatically
320: * <i>grown</i> to the appropriate size.</p>
321: *
322: * @param collection The Collection of new elements.
323: * @param index The index position to insert the new elements at.
324: * @return true if elements were added.
325: */
326: public boolean addAll(int index, Collection collection) {
327:
328: if (collection == null || collection.size() == 0) {
329: return false;
330: }
331:
332: ensureCapacity((index > size() ? index : size())
333: + collection.size());
334:
335: // Call "tranform" with first element, before
336: // List is "grown" to ensure the correct DynaClass
337: // is set.
338: if (size() == 0) {
339: transform(collection.iterator().next());
340: }
341:
342: growList(index);
343:
344: Iterator iterator = collection.iterator();
345: while (iterator.hasNext()) {
346: add(index++, iterator.next());
347: }
348:
349: return true;
350:
351: }
352:
353: /**
354: * <p>Return the element at the specified position.</p>
355: *
356: * <p>If the position requested is greater than the current
357: * size of the List, then the List is automatically
358: * <i>grown</i> (and populated) to the appropriate size.</p>
359: *
360: * @param index The index position to insert the new elements at.
361: * @return The element at the specified position.
362: */
363: public Object get(int index) {
364:
365: growList(index + 1);
366:
367: return super .get(index);
368:
369: }
370:
371: /**
372: * <p>Set the element at the specified position.</p>
373: *
374: * <p>If the position requested is greater than the current
375: * size of the List, then the List is automatically
376: * <i>grown</i> (and populated) to the appropriate size.</p>
377: *
378: * @param index The index position to insert the new element at.
379: * @param element The new element.
380: * @return The new element.
381: */
382: public Object set(int index, Object element) {
383:
384: DynaBean dynaBean = transform(element);
385:
386: growList(index + 1);
387:
388: return super .set(index, dynaBean);
389:
390: }
391:
392: /**
393: * <p>Converts the List to an Array.</p>
394: *
395: * <p>The type of Array created depends on the contents
396: * of the List:</p>
397: * <ul>
398: * <li>If the List contains only LazyDynaMap type elements
399: * then a java.util.Map[] array will be created.</li>
400: * <li>If the List contains only elements which are
401: * "wrapped" DynaBeans then an Object[] of the most
402: * suitable type will be created.</li>
403: * <li>...otherwise a DynaBean[] will be created.</li>
404: *
405: * @return An Array of the elements in this List.
406: */
407: public Object[] toArray() {
408:
409: if (size() == 0 && elementType == null) {
410: return new LazyDynaBean[0];
411: }
412:
413: Object[] array = (Object[]) Array.newInstance(elementType,
414: 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
439: .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: // ------------------- 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(
485: 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(
504: "Element Type is missing");
505: }
506:
507: if (size() > 0) {
508: throw new IllegalStateException(
509: "Element Type cannot be reset");
510: }
511:
512: this .elementType = elementType;
513:
514: // Create a new object of the specified type
515: Object object = null;
516: try {
517: object = elementType.newInstance();
518: } catch (Exception e) {
519: throw new IllegalArgumentException("Error creating type: "
520: + elementType.getName() + " - " + e);
521: }
522:
523: // Create a DynaBean
524: DynaBean dynaBean = null;
525: if (Map.class.isAssignableFrom(elementType)) {
526: dynaBean = new LazyDynaMap((Map) object);
527: this .elementDynaClass = dynaBean.getDynaClass();
528: } else if (DynaBean.class.isAssignableFrom(elementType)) {
529: dynaBean = (DynaBean) object;
530: this .elementDynaClass = dynaBean.getDynaClass();
531: } else {
532: dynaBean = new WrapDynaBean(object);
533: this .wrapDynaClass = (WrapDynaClass) dynaBean
534: .getDynaClass();
535: }
536:
537: this .elementDynaBeanType = dynaBean.getClass();
538:
539: // Re-calculate the type
540: if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType)) {
541: this .elementType = ((WrapDynaBean) dynaBean).getInstance()
542: .getClass();
543: } else if (LazyDynaMap.class
544: .isAssignableFrom(elementDynaBeanType)) {
545: this .elementType = ((LazyDynaMap) dynaBean).getMap()
546: .getClass();
547: }
548:
549: }
550:
551: /**
552: * <p>Set the element Type and DynaClass.</p>
553: *
554: * @param elementDynaClass The DynaClass of the elements.
555: * @exception IllegalArgumentException if the List already
556: * contains elements or the DynaClass is null.
557: */
558: public void setElementDynaClass(DynaClass elementDynaClass) {
559:
560: if (elementDynaClass == null) {
561: throw new IllegalArgumentException(
562: "Element DynaClass is missing");
563: }
564:
565: if (size() > 0) {
566: throw new IllegalStateException(
567: "Element DynaClass cannot be reset");
568: }
569:
570: // Try to create a new instance of the DynaBean
571: try {
572: DynaBean dynaBean = elementDynaClass.newInstance();
573: this .elementDynaBeanType = dynaBean.getClass();
574: if (WrapDynaBean.class
575: .isAssignableFrom(elementDynaBeanType)) {
576: this .elementType = ((WrapDynaBean) dynaBean)
577: .getInstance().getClass();
578: this .wrapDynaClass = (WrapDynaClass) elementDynaClass;
579: } else if (LazyDynaMap.class
580: .isAssignableFrom(elementDynaBeanType)) {
581: this .elementType = ((LazyDynaMap) dynaBean).getMap()
582: .getClass();
583: this .elementDynaClass = elementDynaClass;
584: } else {
585: this .elementType = dynaBean.getClass();
586: this .elementDynaClass = elementDynaClass;
587: }
588: } catch (Exception e) {
589: throw new IllegalArgumentException(
590: "Error creating DynaBean from "
591: + elementDynaClass.getClass().getName()
592: + " - " + e);
593: }
594:
595: }
596:
597: // ------------------- Private Methods ---------------------------
598:
599: /**
600: * <p>Automatically <i>grown</i> the List
601: * to the appropriate size, populating with
602: * DynaBeans.</p>
603: *
604: * @param requiredSize the required size of the List.
605: */
606: private void growList(int requiredSize) {
607:
608: if (requiredSize < size()) {
609: return;
610: }
611:
612: ensureCapacity(requiredSize + 1);
613:
614: for (int i = size(); i < requiredSize; i++) {
615: DynaBean dynaBean = transform(null);
616: super .add(dynaBean);
617: }
618:
619: }
620:
621: /**
622: * <p>Transform the element into a DynaBean:</p>
623: *
624: * <ul>
625: * <li>Map elements are turned into LazyDynaMap's.</li>
626: * <li>POJO Beans are "wrapped" in a WrapDynaBean.</li>
627: * <li>DynaBeans are unchanged.</li>
628: * </li>
629: *
630: * @param element The element to transformt.
631: * @param The DynaBean to store in the List.
632: */
633: private DynaBean transform(Object element) {
634:
635: DynaBean dynaBean = null;
636: Class newDynaBeanType = null;
637: Class newElementType = null;
638:
639: // Create a new element
640: if (element == null) {
641:
642: // Default Types to LazyDynaBean
643: // if not specified
644: if (elementType == null) {
645: setElementDynaClass(new LazyDynaClass());
646: }
647:
648: // Get DynaClass (restore WrapDynaClass lost in serialization)
649: DynaClass dynaClass = (elementDynaClass == null) ? wrapDynaClass
650: : elementDynaClass;
651: if (dynaClass == null) {
652: setElementType(elementType);
653: }
654:
655: // Create a new DynaBean
656: try {
657: dynaBean = dynaClass.newInstance();
658: newDynaBeanType = dynaBean.getClass();
659: } catch (Exception e) {
660: throw new IllegalArgumentException(
661: "Error creating DynaBean: "
662: + dynaClass.getClass().getName()
663: + " - " + e);
664: }
665:
666: } else {
667:
668: // Transform Object to a DynaBean
669: newElementType = element.getClass();
670: if (Map.class.isAssignableFrom(element.getClass())) {
671: dynaBean = new LazyDynaMap((Map) element);
672: } else if (DynaBean.class.isAssignableFrom(element
673: .getClass())) {
674: dynaBean = (DynaBean) element;
675: } else {
676: dynaBean = new WrapDynaBean(element);
677: }
678:
679: newDynaBeanType = dynaBean.getClass();
680:
681: }
682:
683: // Re-calculate the element type
684: newElementType = dynaBean.getClass();
685: if (WrapDynaBean.class.isAssignableFrom(newDynaBeanType)) {
686: newElementType = ((WrapDynaBean) dynaBean).getInstance()
687: .getClass();
688: } else if (LazyDynaMap.class.isAssignableFrom(newDynaBeanType)) {
689: newElementType = ((LazyDynaMap) dynaBean).getMap()
690: .getClass();
691: }
692:
693: // Check the new element type, matches all the
694: // other elements in the List
695: if (newElementType != elementType) {
696: throw new IllegalArgumentException("Element Type "
697: + newElementType + " doesn't match other elements "
698: + elementType);
699: }
700:
701: return dynaBean;
702:
703: }
704:
705: }
|