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.lang.reflect.Array; 20 import java.util.ArrayList; 21 import java.util.Collection; 22 import java.util.Map; 23 24 /** 25 * <h2><i>Lazy</i> DynaBean List.</h2> 26 * 27 * <p>There are two main purposes for this class:</p> 28 * <ul> 29 * <li>To provide <i>Lazy List</i> behaviour - automatically 30 * <i>growing</i> and <i>populating</i> the <code>List</code> 31 * with either <code>DynaBean</code>, <code>java.util.Map</code> 32 * or POJO Beans.</li> 33 * <li>To provide a straight forward way of putting a Collection 34 * or Array into the lazy list <i>and</i> a straight forward 35 * way to get it out again at the end.</li> 36 * </ul> 37 * 38 * <p>All elements added to the List are stored as <code>DynaBean</code>'s:</p> 39 * <ul> 40 * <li><code>java.util.Map</code> elements are "wrapped" in a <code>LazyDynaMap</code>.</i> 41 * <li>POJO Bean elements are "wrapped" in a <code>WrapDynaBean.</code></i> 42 * <li><code>DynaBean</code>'s are stored un-changed.</i> 43 * </ul> 44 * 45 * <h4><code>toArray()</code></h4> 46 * <p>The <code>toArray()</code> method returns an array of the 47 * elements of the appropriate type. If the <code>LazyDynaList</code> 48 * is populated with <code>java.util.Map</code> objects a 49 * <code>Map[]</code> array is returned. 50 * If the list is populated with POJO Beans an appropriate 51 * array of the POJO Beans is returned. Otherwise a <code>DynaBean[]</code> 52 * array is returned. 53 * </p> 54 * 55 * <h4><code>toDynaBeanArray()</code></h4> 56 * <p>The <code>toDynaBeanArray()</code> method returns a 57 * <code>DynaBean[]</code> array of the elements in the List. 58 * </p> 59 * 60 * <p><strong>N.B.</strong>All the elements in the List must be the 61 * same type. If the <code>DynaClass</code> or <code>Class</code> 62 * of the <code>LazyDynaList</code>'s elements is 63 * not specified, then it will be automatically set to the type 64 * of the first element populated. 65 * </p> 66 * 67 * <h3>Example 1</h3> 68 * <p>If you have an array of <code>java.util.Map[]</code> - you can put that into 69 * a <code>LazyDynaList</code>.</p> 70 * 71 * <pre><code> 72 * TreeMap[] myArray = .... // your Map[] 73 * List lazyList = new LazyDynaList(myArray); 74 * </code></pre> 75 * 76 * <p>New elements of the appropriate Map type are 77 * automatically populated:</p> 78 * 79 * <pre><code> 80 * // get(index) automatically grows the list 81 * DynaBean newElement = (DynaBean)lazyList.get(lazyList.size()); 82 * newElement.put("someProperty", "someValue"); 83 * </code></pre> 84 * 85 * <p>Once you've finished you can get back an Array of the 86 * elements of the appropriate type:</p> 87 * 88 * <pre><code> 89 * // Retrieve the array from the list 90 * TreeMap[] myArray = (TreeMap[])lazyList.toArray()); 91 * </code></pre> 92 * 93 * 94 * <h3>Example 2</h3> 95 * <p>Alternatively you can create an <i>empty</i> List and 96 * specify the Class for List's elements. The LazyDynaList 97 * uses the Class to automatically populate elements:</p> 98 * 99 * <pre><code> 100 * // e.g. For Maps 101 * List lazyList = new LazyDynaList(TreeMap.class); 102 * 103 * // e.g. For POJO Beans 104 * List lazyList = new LazyDynaList(MyPojo.class); 105 * 106 * // e.g. For DynaBeans 107 * List lazyList = new LazyDynaList(MyDynaBean.class); 108 * </code></pre> 109 * 110 * <h3>Example 3</h3> 111 * <p>Alternatively you can create an <i>empty</i> List and specify the 112 * DynaClass for List's elements. The LazyDynaList uses 113 * the DynaClass to automatically populate elements:</p> 114 * 115 * <pre><code> 116 * // e.g. For Maps 117 * DynaClass dynaClass = new LazyDynaMap(new HashMap()); 118 * List lazyList = new LazyDynaList(dynaClass); 119 * 120 * // e.g. For POJO Beans 121 * DynaClass dynaClass = (new WrapDynaBean(myPojo)).getDynaClass(); 122 * List lazyList = new LazyDynaList(dynaClass); 123 * 124 * // e.g. For DynaBeans 125 * DynaClass dynaClass = new BasicDynaClass(properties); 126 * List lazyList = new LazyDynaList(dynaClass); 127 * </code></pre> 128 * 129 * <p><strong>N.B.</strong> You may wonder why control the type 130 * using a <code>DynaClass</code> rather than the <code>Class</code> 131 * as in the previous example - the reason is that some <code>DynaBean</code> 132 * implementations don't have a <i>default</i> empty constructor and 133 * therefore need to be instantiated using the <code>DynaClass.newInstance()</code> 134 * method.</p> 135 * 136 * <h3>Example 4</h3> 137 * <p>A slight variation - set the element type using either 138 * the <code>setElementType(Class)</code> method or the 139 * <code>setElementDynaClass(DynaClass)</code> method - then populate 140 * with the normal <code>java.util.List</code> methods(i.e. 141 * <code>add()</code>, <code>addAll()</code> or <code>set()</code>).</p> 142 * 143 * <pre><code> 144 * // Create a new LazyDynaList (100 element capacity) 145 * LazyDynaList lazyList = new LazyDynaList(100); 146 * 147 * // Either Set the element type... 148 * lazyList.setElementType(TreeMap.class); 149 * 150 * // ...or the element DynaClass... 151 * lazyList.setElementDynaClass(new MyCustomDynaClass()); 152 * 153 * // Populate from a collection 154 * lazyList.addAll(myCollection); 155 * 156 * </code></pre> 157 * 158 * @version $Id$ 159 * @since 1.8.0 160 */ 161 public class LazyDynaList extends ArrayList<Object> { 162 163 /** 164 * The DynaClass of the List's elements. 165 */ 166 private DynaClass elementDynaClass; 167 168 /** 169 * The WrapDynaClass if the List's contains 170 * POJO Bean elements. 171 * 172 * N.B. WrapDynaClass isn't serlializable, which 173 * is why its stored separately in a 174 * transient instance variable. 175 */ 176 private transient WrapDynaClass wrapDynaClass; 177 178 /** 179 * The type of the List's elements. 180 */ 181 private Class<?> elementType; 182 183 /** 184 * The DynaBean type of the List's elements. 185 */ 186 private Class<?> elementDynaBeanType; 187 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(final 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(final 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(final 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 populate the List from. 236 */ 237 public LazyDynaList(final 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 populate the List from. 247 */ 248 public LazyDynaList(final Object[] array) { 249 super(array.length); 250 for (Object element : array) { 251 add(element); 252 } 253 } 254 255 256 // ------------------- java.util.List Methods -------------------- 257 258 /** 259 * <p>Insert an element at the specified index position.</p> 260 * 261 * <p>If the index position is greater than the current 262 * size of the List, then the List is automatically 263 * <i>grown</i> to the appropriate size.</p> 264 * 265 * @param index The index position to insert the new element. 266 * @param element The new element to add. 267 */ 268 @Override 269 public void add(final int index, final Object element) { 270 271 final 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 @Override 286 public boolean add(final Object element) { 287 288 final 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 @Override 301 public boolean addAll(final Collection<?> collection) { 302 303 if (collection == null || collection.size() == 0) { 304 return false; 305 } 306 307 ensureCapacity(size() + collection.size()); 308 309 for (final Object e : collection) { 310 add(e); 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 @Override 330 public boolean addAll(final int index, final Collection<?> collection) { 331 332 if (collection == null || collection.size() == 0) { 333 return false; 334 } 335 336 ensureCapacity((index > size() ? index : size()) + collection.size()); 337 338 // Call "transform" with first element, before 339 // List is "grown" to ensure the correct DynaClass 340 // is set. 341 if (size() == 0) { 342 transform(collection.iterator().next()); 343 } 344 345 growList(index); 346 347 int currentIndex = index; 348 for (final Object e : collection) { 349 add(currentIndex++, e); 350 } 351 352 return true; 353 354 } 355 356 /** 357 * <p>Return the element at the specified position.</p> 358 * 359 * <p>If the position requested is greater than the current 360 * size of the List, then the List is automatically 361 * <i>grown</i> (and populated) to the appropriate size.</p> 362 * 363 * @param index The index position to insert the new elements at. 364 * @return The element at the specified position. 365 */ 366 @Override 367 public Object get(final int index) { 368 369 growList(index + 1); 370 371 return super.get(index); 372 373 } 374 375 /** 376 * <p>Set the element at the specified position.</p> 377 * 378 * <p>If the position requested is greater than the current 379 * size of the List, then the List is automatically 380 * <i>grown</i> (and populated) to the appropriate size.</p> 381 * 382 * @param index The index position to insert the new element at. 383 * @param element The new element. 384 * @return The new element. 385 */ 386 @Override 387 public Object set(final int index, final Object element) { 388 389 final DynaBean dynaBean = transform(element); 390 391 growList(index + 1); 392 393 return super.set(index, dynaBean); 394 395 } 396 397 /** 398 * <p>Converts the List to an Array.</p> 399 * 400 * <p>The type of Array created depends on the contents 401 * of the List:</p> 402 * <ul> 403 * <li>If the List contains only LazyDynaMap type elements 404 * then a java.util.Map[] array will be created.</li> 405 * <li>If the List contains only elements which are 406 * "wrapped" DynaBeans then an Object[] of the most 407 * suitable type will be created.</li> 408 * <li>...otherwise a DynaBean[] will be created.</li> 409 * 410 * @return An Array of the elements in this List. 411 */ 412 @Override 413 public Object[] toArray() { 414 415 if (size() == 0 && elementType == null) { 416 return new LazyDynaBean[0]; 417 } 418 419 final Object[] array = (Object[])Array.newInstance(elementType, size()); 420 for (int i = 0; i < size(); i++) { 421 if (Map.class.isAssignableFrom(elementType)) { 422 array[i] = ((LazyDynaMap)get(i)).getMap(); 423 } else if (DynaBean.class.isAssignableFrom(elementType)) { 424 array[i] = get(i); 425 } else { 426 array[i] = ((WrapDynaBean)get(i)).getInstance(); 427 } 428 } 429 return array; 430 431 } 432 433 /** 434 * <p>Converts the List to an Array of the specified type.</p> 435 * 436 * @param <T> The type of the array elements 437 * @param model The model for the type of array to return 438 * @return An Array of the elements in this List. 439 */ 440 @Override 441 public <T> T[] toArray(final T[] model) { 442 443 final Class<?> arrayType = model.getClass().getComponentType(); 444 if ((DynaBean.class.isAssignableFrom(arrayType)) 445 || (size() == 0 && elementType == null)) { 446 return super.toArray(model); 447 } 448 449 if ((arrayType.isAssignableFrom(elementType))) { 450 T[] array; 451 if (model.length >= size()) { 452 array = model; 453 } else { 454 @SuppressWarnings("unchecked") 455 final 456 // This is safe because we know the element type 457 T[] tempArray = (T[]) Array.newInstance(arrayType, size()); 458 array = tempArray; 459 } 460 461 for (int i = 0; i < size(); i++) { 462 Object elem; 463 if (Map.class.isAssignableFrom(elementType)) { 464 elem = ((LazyDynaMap) get(i)).getMap(); 465 } else if (DynaBean.class.isAssignableFrom(elementType)) { 466 elem = get(i); 467 } else { 468 elem = ((WrapDynaBean) get(i)).getInstance(); 469 } 470 Array.set(array, i, elem); 471 } 472 return array; 473 } 474 475 throw new IllegalArgumentException("Invalid array type: " 476 + arrayType.getName() + " - not compatible with '" 477 + elementType.getName()); 478 479 } 480 481 482 // ------------------- Public Methods ---------------------------- 483 484 /** 485 * <p>Converts the List to an DynaBean Array.</p> 486 * 487 * @return A DynaBean[] of the elements in this List. 488 */ 489 public DynaBean[] toDynaBeanArray() { 490 491 if (size() == 0 && elementDynaBeanType == null) { 492 return new LazyDynaBean[0]; 493 } 494 495 final DynaBean[] array = (DynaBean[])Array.newInstance(elementDynaBeanType, size()); 496 for (int i = 0; i < size(); i++) { 497 array[i] = (DynaBean)get(i); 498 } 499 return array; 500 501 } 502 503 /** 504 * <p>Set the element Type and DynaClass.</p> 505 * 506 * @param elementType The type of the elements. 507 * @throws IllegalArgumentException if the List already 508 * contains elements or the DynaClass is null. 509 */ 510 public void setElementType(final Class<?> elementType) { 511 512 if (elementType == null) { 513 throw new IllegalArgumentException("Element Type is missing"); 514 } 515 516 final boolean changeType = (this.elementType != null && !this.elementType.equals(elementType)); 517 if (changeType && size() > 0) { 518 throw new IllegalStateException("Element Type cannot be reset"); 519 } 520 521 this.elementType = elementType; 522 523 // Create a new object of the specified type 524 Object object = null; 525 try { 526 object = elementType.newInstance(); 527 } catch (final Exception e) { 528 throw new IllegalArgumentException("Error creating type: " 529 + elementType.getName() + " - " + e); 530 } 531 532 // Create a DynaBean 533 DynaBean dynaBean = null; 534 if (Map.class.isAssignableFrom(elementType)) { 535 dynaBean = createDynaBeanForMapProperty(object); 536 this.elementDynaClass = dynaBean.getDynaClass(); 537 } else if (DynaBean.class.isAssignableFrom(elementType)) { 538 dynaBean = (DynaBean)object; 539 this.elementDynaClass = dynaBean.getDynaClass(); 540 } else { 541 dynaBean = new WrapDynaBean(object); 542 this.wrapDynaClass = (WrapDynaClass)dynaBean.getDynaClass(); 543 } 544 545 this.elementDynaBeanType = dynaBean.getClass(); 546 547 // Re-calculate the type 548 if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType )) { 549 this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass(); 550 } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType )) { 551 this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass(); 552 } 553 554 } 555 556 /** 557 * <p>Set the element Type and DynaClass.</p> 558 * 559 * @param elementDynaClass The DynaClass of the elements. 560 * @throws IllegalArgumentException if the List already 561 * contains elements or the DynaClass is null. 562 */ 563 public void setElementDynaClass(final DynaClass elementDynaClass) { 564 565 if (elementDynaClass == null) { 566 throw new IllegalArgumentException("Element DynaClass is missing"); 567 } 568 569 if (size() > 0) { 570 throw new IllegalStateException("Element DynaClass cannot be reset"); 571 } 572 573 // Try to create a new instance of the DynaBean 574 try { 575 final DynaBean dynaBean = elementDynaClass.newInstance(); 576 this.elementDynaBeanType = dynaBean.getClass(); 577 if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType)) { 578 this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass(); 579 this.wrapDynaClass = (WrapDynaClass)elementDynaClass; 580 } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType)) { 581 this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass(); 582 this.elementDynaClass = elementDynaClass; 583 } else { 584 this.elementType = dynaBean.getClass(); 585 this.elementDynaClass = elementDynaClass; 586 } 587 } catch (final Exception e) { 588 throw new IllegalArgumentException( 589 "Error creating DynaBean from " + 590 elementDynaClass.getClass().getName() + " - " + e); 591 } 592 593 } 594 595 596 // ------------------- Private Methods --------------------------- 597 598 /** 599 * <p>Automatically <i>grown</i> the List 600 * to the appropriate size, populating with 601 * DynaBeans.</p> 602 * 603 * @param requiredSize the required size of the List. 604 */ 605 private void growList(final int requiredSize) { 606 607 if (requiredSize < size()) { 608 return; 609 } 610 611 ensureCapacity(requiredSize + 1); 612 613 for (int i = size(); i < requiredSize; i++) { 614 final DynaBean dynaBean = transform(null); 615 super.add(dynaBean); 616 } 617 618 } 619 620 /** 621 * <p>Transform the element into a DynaBean:</p> 622 * 623 * <ul> 624 * <li>Map elements are turned into LazyDynaMap's.</li> 625 * <li>POJO Beans are "wrapped" in a WrapDynaBean.</li> 626 * <li>DynaBeans are unchanged.</li> 627 * </li> 628 * 629 * @param element The element to transformed. 630 * @param The DynaBean to store in the List. 631 */ 632 private DynaBean transform(final Object element) { 633 634 DynaBean dynaBean = null; 635 Class<?> newDynaBeanType = null; 636 Class<?> newElementType = null; 637 638 // Create a new element 639 if (element == null) { 640 641 // Default Types to LazyDynaBean 642 // if not specified 643 if (elementType == null) { 644 setElementDynaClass(new LazyDynaClass()); 645 } 646 647 // Get DynaClass (restore WrapDynaClass lost in serialization) 648 if (getDynaClass() == null) { 649 setElementType(elementType); 650 } 651 652 // Create a new DynaBean 653 try { 654 dynaBean = getDynaClass().newInstance(); 655 newDynaBeanType = dynaBean.getClass(); 656 } catch (final Exception e) { 657 throw new IllegalArgumentException("Error creating DynaBean: " 658 + getDynaClass().getClass().getName() 659 + " - " + e); 660 } 661 662 } else { 663 664 // Transform Object to a DynaBean 665 newElementType = element.getClass(); 666 if (Map.class.isAssignableFrom(element.getClass())) { 667 dynaBean = createDynaBeanForMapProperty(element); 668 } else if (DynaBean.class.isAssignableFrom(element.getClass())) { 669 dynaBean = (DynaBean)element; 670 } else { 671 dynaBean = new WrapDynaBean(element); 672 } 673 674 newDynaBeanType = dynaBean.getClass(); 675 676 } 677 678 // Re-calculate the element type 679 newElementType = dynaBean.getClass(); 680 if (WrapDynaBean.class.isAssignableFrom(newDynaBeanType)) { 681 newElementType = ((WrapDynaBean)dynaBean).getInstance().getClass(); 682 } else if (LazyDynaMap.class.isAssignableFrom(newDynaBeanType)) { 683 newElementType = ((LazyDynaMap)dynaBean).getMap().getClass(); 684 } 685 686 // Check the new element type, matches all the 687 // other elements in the List 688 if (elementType != null && !newElementType.equals(elementType)) { 689 throw new IllegalArgumentException("Element Type " + newElementType 690 + " doesn't match other elements " + elementType); 691 } 692 693 return dynaBean; 694 695 } 696 697 /** 698 * Creates a new {@code LazyDynaMap} object for the given property value. 699 * 700 * @param value the property value 701 * @return the newly created {@code LazyDynaMap} 702 */ 703 private LazyDynaMap createDynaBeanForMapProperty(final Object value) { 704 @SuppressWarnings("unchecked") 705 final 706 // map properties are always stored as Map<String, Object> 707 Map<String, Object> valueMap = (Map<String, Object>) value; 708 return new LazyDynaMap(valueMap); 709 } 710 711 /** 712 * Return the DynaClass. 713 */ 714 private DynaClass getDynaClass() { 715 return (elementDynaClass == null ? wrapDynaClass : elementDynaClass); 716 } 717 }