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 }