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 * https://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 */ 017package org.apache.commons.beanutils; 018 019import java.lang.reflect.Array; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Map; 023 024/** 025 * <h2><em>Lazy</em> DynaBean List.</h2> 026 * 027 * <p>There are two main purposes for this class:</p> 028 * <ul> 029 * <li>To provide <em>Lazy List</em> behavior - automatically 030 * <em>growing</em> and <em>populating</em> the <code>List</code> 031 * with either <code>DynaBean</code>, <code>java.util.Map</code> 032 * or POJO Beans.</li> 033 * <li>To provide a straight forward way of putting a Collection 034 * or Array into the lazy list <em>and</em> a straight forward 035 * way to get it out again at the end.</li> 036 * </ul> 037 * 038 * <p>All elements added to the List are stored as <code>DynaBean</code>'s:</p> 039 * <ul> 040 * <li><code>java.util.Map</code> elements are "wrapped" in a <code>LazyDynaMap</code>.</li> 041 * <li>POJO Bean elements are "wrapped" in a <code>WrapDynaBean.</code></li> 042 * <li><code>DynaBean</code>'s are stored un-changed.</li> 043 * </ul> 044 * 045 * <h3><code>toArray()</code></h3> 046 * <p>The <code>toArray()</code> method returns an array of the 047 * elements of the appropriate type. If the <code>LazyDynaList</code> 048 * is populated with <code>java.util.Map</code> objects a 049 * <code>Map[]</code> array is returned. 050 * If the list is populated with POJO Beans an appropriate 051 * array of the POJO Beans is returned. Otherwise a <code>DynaBean[]</code> 052 * array is returned. 053 * </p> 054 * 055 * <h3><code>toDynaBeanArray()</code></h3> 056 * <p>The <code>toDynaBeanArray()</code> method returns a 057 * <code>DynaBean[]</code> array of the elements in the List. 058 * </p> 059 * 060 * <p><strong>N.B.</strong>All the elements in the List must be the 061 * same type. If the <code>DynaClass</code> or <code>Class</code> 062 * of the <code>LazyDynaList</code>'s elements is 063 * not specified, then it will be automatically set to the type 064 * of the first element populated. 065 * </p> 066 * 067 * <h3>Example 1</h3> 068 * <p>If you have an array of <code>java.util.Map[]</code> - you can put that into 069 * a <code>LazyDynaList</code>.</p> 070 * 071 * <pre><code> 072 * TreeMap[] myArray = .... // your Map[] 073 * List lazyList = new LazyDynaList(myArray); 074 * </code></pre> 075 * 076 * <p>New elements of the appropriate Map type are 077 * automatically populated:</p> 078 * 079 * <pre><code> 080 * // get(index) automatically grows the list 081 * DynaBean newElement = (DynaBean)lazyList.get(lazyList.size()); 082 * newElement.put("someProperty", "someValue"); 083 * </code></pre> 084 * 085 * <p>Once you've finished you can get back an Array of the 086 * elements of the appropriate type:</p> 087 * 088 * <pre><code> 089 * // Retrieve the array from the list 090 * TreeMap[] myArray = (TreeMap[])lazyList.toArray()); 091 * </code></pre> 092 * 093 * 094 * <h3>Example 2</h3> 095 * <p>Alternatively you can create an <em>empty</em> List and 096 * specify the Class for List's elements. The LazyDynaList 097 * uses the Class to automatically populate elements:</p> 098 * 099 * <pre><code> 100 * // for example For Maps 101 * List lazyList = new LazyDynaList(TreeMap.class); 102 * 103 * // for example For POJO Beans 104 * List lazyList = new LazyDynaList(MyPojo.class); 105 * 106 * // for example 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 <em>empty</em> 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 * // for example For Maps 117 * DynaClass dynaClass = new LazyDynaMap(new HashMap()); 118 * List lazyList = new LazyDynaList(dynaClass); 119 * 120 * // for example For POJO Beans 121 * DynaClass dynaClass = (new WrapDynaBean(myPojo)).getDynaClass(); 122 * List lazyList = new LazyDynaList(dynaClass); 123 * 124 * // for example 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 <em>default</em> 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 * @since 1.8.0 159 */ 160public class LazyDynaList extends ArrayList<Object> { 161 162 private static final long serialVersionUID = 1L; 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 * 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 * Constructs a new instance. 191 */ 192 public LazyDynaList() { 193 } 194 195 /** 196 * Construct a LazyDynaList with a 197 * specified type for its elements. 198 * 199 * @param elementType The Type of the List's elements. 200 */ 201 public LazyDynaList(final Class<?> elementType) { 202 setElementType(elementType); 203 } 204 205 /** 206 * Construct a LazyDynaList populated with the 207 * elements of a Collection. 208 * 209 * @param collection The Collection to populate the List from. 210 */ 211 public LazyDynaList(final Collection<?> collection) { 212 super(collection.size()); 213 addAll(collection); 214 } 215 216 /** 217 * Construct a LazyDynaList with a 218 * specified DynaClass for its elements. 219 * 220 * @param elementDynaClass The DynaClass of the List's elements. 221 */ 222 public LazyDynaList(final DynaClass elementDynaClass) { 223 setElementDynaClass(elementDynaClass); 224 } 225 226 /** 227 * Construct a LazyDynaList with the 228 * specified capacity. 229 * 230 * @param capacity The initial capacity of the list. 231 */ 232 public LazyDynaList(final int capacity) { 233 super(capacity); 234 235 } 236 237 /** 238 * Construct a LazyDynaList populated with the 239 * elements of an Array. 240 * 241 * @param array The Array to populate the List from. 242 */ 243 public LazyDynaList(final Object[] array) { 244 super(array.length); 245 for (final Object element : array) { 246 add(element); 247 } 248 } 249 250 /** 251 * <p>Insert an element at the specified index position.</p> 252 * 253 * <p>If the index position is greater than the current 254 * size of the List, then the List is automatically 255 * <em>grown</em> to the appropriate size.</p> 256 * 257 * @param index The index position to insert the new element. 258 * @param element The new element to add. 259 */ 260 @Override 261 public void add(final int index, final Object element) { 262 263 final DynaBean dynaBean = transform(element); 264 265 growList(index); 266 267 super.add(index, dynaBean); 268 269 } 270 271 /** 272 * <p>Add an element to the List.</p> 273 * 274 * @param element The new element to add. 275 * @return true. 276 */ 277 @Override 278 public boolean add(final Object element) { 279 280 final DynaBean dynaBean = transform(element); 281 282 return super.add(dynaBean); 283 284 } 285 286 /** 287 * <p>Add all the elements from a Collection to the list. 288 * 289 * @param collection The Collection of new elements. 290 * @return true if elements were added. 291 */ 292 @Override 293 public boolean addAll(final Collection<?> collection) { 294 295 if (collection == null || collection.size() == 0) { 296 return false; 297 } 298 299 ensureCapacity(size() + collection.size()); 300 301 for (final Object e : collection) { 302 add(e); 303 } 304 305 return true; 306 307 } 308 309 /** 310 * <p>Insert all the elements from a Collection into the 311 * list at a specified position. 312 * 313 * <p>If the index position is greater than the current 314 * size of the List, then the List is automatically 315 * <em>grown</em> to the appropriate size.</p> 316 * 317 * @param collection The Collection of new elements. 318 * @param index The index position to insert the new elements at. 319 * @return true if elements were added. 320 */ 321 @Override 322 public boolean addAll(final int index, final Collection<?> collection) { 323 324 if (collection == null || collection.size() == 0) { 325 return false; 326 } 327 328 ensureCapacity((index > size() ? index : size()) + collection.size()); 329 330 // Call "transform" with first element, before 331 // List is "grown" to ensure the correct DynaClass 332 // is set. 333 if (size() == 0) { 334 transform(collection.iterator().next()); 335 } 336 337 growList(index); 338 339 int currentIndex = index; 340 for (final Object e : collection) { 341 add(currentIndex++, e); 342 } 343 344 return true; 345 346 } 347 348 /** 349 * Creates a new {@code LazyDynaMap} object for the given property value. 350 * 351 * @param value the property value 352 * @return the newly created {@code LazyDynaMap} 353 */ 354 private LazyDynaMap createDynaBeanForMapProperty(final Object value) { 355 @SuppressWarnings("unchecked") 356 final 357 // map properties are always stored as Map<String, Object> 358 Map<String, Object> valueMap = (Map<String, Object>) value; 359 return new LazyDynaMap(valueMap); 360 } 361 362 /** 363 * <p>Return the element at the specified position.</p> 364 * 365 * <p>If the position requested is greater than the current 366 * size of the List, then the List is automatically 367 * <em>grown</em> (and populated) to the appropriate size.</p> 368 * 369 * @param index The index position to insert the new elements at. 370 * @return The element at the specified position. 371 */ 372 @Override 373 public Object get(final int index) { 374 375 growList(index + 1); 376 377 return super.get(index); 378 379 } 380 381 /** 382 * Return the DynaClass. 383 */ 384 private DynaClass getDynaClass() { 385 return elementDynaClass == null ? wrapDynaClass : elementDynaClass; 386 } 387 388 /** 389 * <p>Automatically <em>grown</em> the List 390 * to the appropriate size, populating with 391 * DynaBeans.</p> 392 * 393 * @param requiredSize the required size of the List. 394 */ 395 private void growList(final int requiredSize) { 396 397 if (requiredSize < size()) { 398 return; 399 } 400 401 ensureCapacity(requiredSize + 1); 402 403 for (int i = size(); i < requiredSize; i++) { 404 final DynaBean dynaBean = transform(null); 405 super.add(dynaBean); 406 } 407 408 } 409 410 /** 411 * <p>Set the element at the specified position.</p> 412 * 413 * <p>If the position requested is greater than the current 414 * size of the List, then the List is automatically 415 * <em>grown</em> (and populated) to the appropriate size.</p> 416 * 417 * @param index The index position to insert the new element at. 418 * @param element The new element. 419 * @return The new element. 420 */ 421 @Override 422 public Object set(final int index, final Object element) { 423 424 final DynaBean dynaBean = transform(element); 425 426 growList(index + 1); 427 428 return super.set(index, dynaBean); 429 430 } 431 432 /** 433 * <p>Set the element Type and DynaClass.</p> 434 * 435 * @param elementDynaClass The DynaClass of the elements. 436 * @throws IllegalArgumentException if the List already 437 * contains elements or the DynaClass is null. 438 */ 439 public void setElementDynaClass(final DynaClass elementDynaClass) { 440 441 if (elementDynaClass == null) { 442 throw new IllegalArgumentException("Element DynaClass is missing"); 443 } 444 445 if (size() > 0) { 446 throw new IllegalStateException("Element DynaClass cannot be reset"); 447 } 448 449 // Try to create a new instance of the DynaBean 450 try { 451 final DynaBean dynaBean = elementDynaClass.newInstance(); 452 this.elementDynaBeanType = dynaBean.getClass(); 453 if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType)) { 454 this.elementType = ((WrapDynaBean)dynaBean).getInstance().getClass(); 455 this.wrapDynaClass = (WrapDynaClass)elementDynaClass; 456 } else { 457 if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType)) { 458 this.elementType = ((LazyDynaMap)dynaBean).getMap().getClass(); 459 } else { 460 this.elementType = dynaBean.getClass(); 461 } 462 this.elementDynaClass = elementDynaClass; 463 } 464 } catch (final Exception e) { 465 throw new IllegalArgumentException( 466 "Error creating DynaBean from " + 467 elementDynaClass.getClass().getName() + " - " + e); 468 } 469 470 } 471 472 /** 473 * <p>Set the element Type and DynaClass.</p> 474 * 475 * @param elementType The type of the elements. 476 * @throws IllegalArgumentException if the List already 477 * contains elements or the DynaClass is null. 478 */ 479 public void setElementType(final Class<?> elementType) { 480 481 if (elementType == null) { 482 throw new IllegalArgumentException("Element Type is missing"); 483 } 484 485 final boolean changeType = this.elementType != null && !this.elementType.equals(elementType); 486 if (changeType && size() > 0) { 487 throw new IllegalStateException("Element Type cannot be reset"); 488 } 489 490 this.elementType = elementType; 491 492 // Create a new object of the specified type 493 Object object = null; 494 try { 495 object = elementType.getConstructor().newInstance(); 496 } catch (final Exception e) { 497 throw new IllegalArgumentException("Error creating type: " 498 + elementType.getName() + " - " + e); 499 } 500 501 // Create a DynaBean 502 DynaBean dynaBean = null; 503 if (Map.class.isAssignableFrom(elementType)) { 504 dynaBean = createDynaBeanForMapProperty(object); 505 this.elementDynaClass = dynaBean.getDynaClass(); 506 } else if (DynaBean.class.isAssignableFrom(elementType)) { 507 dynaBean = (DynaBean)object; 508 this.elementDynaClass = dynaBean.getDynaClass(); 509 } else { 510 dynaBean = new WrapDynaBean(object); 511 this.wrapDynaClass = (WrapDynaClass)dynaBean.getDynaClass(); 512 } 513 514 this.elementDynaBeanType = dynaBean.getClass(); 515 516 // Re-calculate the type 517 if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType)) { 518 this.elementType = ((WrapDynaBean) dynaBean).getInstance().getClass(); 519 } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType)) { 520 this.elementType = ((LazyDynaMap) dynaBean).getMap().getClass(); 521 } 522 523 } 524 525 /** 526 * <p>Converts the List to an Array.</p> 527 * 528 * <p>The type of Array created depends on the contents 529 * of the List:</p> 530 * <ul> 531 * <li>If the List contains only LazyDynaMap type elements 532 * then a java.util.Map[] array will be created.</li> 533 * <li>If the List contains only elements which are 534 * "wrapped" DynaBeans then an Object[] of the most 535 * suitable type will be created.</li> 536 * <li>...otherwise a DynaBean[] will be created.</li> 537 * </ul> 538 * 539 * @return An Array of the elements in this List. 540 */ 541 @Override 542 public Object[] toArray() { 543 544 if (size() == 0 && elementType == null) { 545 return new LazyDynaBean[0]; 546 } 547 548 final Object[] array = (Object[])Array.newInstance(elementType, size()); 549 for (int i = 0; i < size(); i++) { 550 if (Map.class.isAssignableFrom(elementType)) { 551 array[i] = ((LazyDynaMap)get(i)).getMap(); 552 } else if (DynaBean.class.isAssignableFrom(elementType)) { 553 array[i] = get(i); 554 } else { 555 array[i] = ((WrapDynaBean)get(i)).getInstance(); 556 } 557 } 558 return array; 559 560 } 561 562 /** 563 * <p>Converts the List to an Array of the specified type.</p> 564 * 565 * @param <T> The type of the array elements 566 * @param model The model for the type of array to return 567 * @return An Array of the elements in this List. 568 */ 569 @Override 570 public <T> T[] toArray(final T[] model) { 571 572 final Class<?> arrayType = model.getClass().getComponentType(); 573 if (DynaBean.class.isAssignableFrom(arrayType) 574 || size() == 0 && elementType == null) { 575 return super.toArray(model); 576 } 577 578 if (arrayType.isAssignableFrom(elementType)) { 579 T[] array; 580 if (model.length >= size()) { 581 array = model; 582 } else { 583 @SuppressWarnings("unchecked") 584 final 585 // This is safe because we know the element type 586 T[] tempArray = (T[]) Array.newInstance(arrayType, size()); 587 array = tempArray; 588 } 589 590 for (int i = 0; i < size(); i++) { 591 Object elem; 592 if (Map.class.isAssignableFrom(elementType)) { 593 elem = ((LazyDynaMap) get(i)).getMap(); 594 } else if (DynaBean.class.isAssignableFrom(elementType)) { 595 elem = get(i); 596 } else { 597 elem = ((WrapDynaBean) get(i)).getInstance(); 598 } 599 Array.set(array, i, elem); 600 } 601 return array; 602 } 603 604 throw new IllegalArgumentException("Invalid array type: " 605 + arrayType.getName() + " - not compatible with '" 606 + elementType.getName()); 607 608 } 609 610 /** 611 * <p>Converts the List to an DynaBean Array.</p> 612 * 613 * @return A DynaBean[] of the elements in this List. 614 */ 615 public DynaBean[] toDynaBeanArray() { 616 617 if (size() == 0 && elementDynaBeanType == null) { 618 return new LazyDynaBean[0]; 619 } 620 621 final DynaBean[] array = (DynaBean[])Array.newInstance(elementDynaBeanType, size()); 622 for (int i = 0; i < size(); i++) { 623 array[i] = (DynaBean)get(i); 624 } 625 return array; 626 627 } 628 629 /** 630 * <p>Transform the element into a DynaBean:</p> 631 * 632 * <ul> 633 * <li>Map elements are turned into LazyDynaMap's.</li> 634 * <li>POJO Beans are "wrapped" in a WrapDynaBean.</li> 635 * <li>DynaBeans are unchanged.</li> 636 * </li> 637 * 638 * @param element The element to transformed. 639 * @param The DynaBean to store in the List. 640 */ 641 private DynaBean transform(final Object element) { 642 643 DynaBean dynaBean = null; 644 Class<?> newDynaBeanType = null; 645 Class<?> newElementType = null; 646 647 // Create a new element 648 if (element == null) { 649 650 // Default Types to LazyDynaBean 651 // if not specified 652 if (elementType == null) { 653 setElementDynaClass(new LazyDynaClass()); 654 } 655 656 // Get DynaClass (restore WrapDynaClass lost in serialization) 657 if (getDynaClass() == null) { 658 setElementType(elementType); 659 } 660 661 // Create a new DynaBean 662 try { 663 dynaBean = getDynaClass().newInstance(); 664 newDynaBeanType = dynaBean.getClass(); 665 } catch (final Exception e) { 666 throw new IllegalArgumentException("Error creating DynaBean: " 667 + getDynaClass().getClass().getName() 668 + " - " + e); 669 } 670 671 } else { 672 673 // Transform Object to a DynaBean 674 newElementType = element.getClass(); 675 if (Map.class.isAssignableFrom(element.getClass())) { 676 dynaBean = createDynaBeanForMapProperty(element); 677 } else if (DynaBean.class.isAssignableFrom(element.getClass())) { 678 dynaBean = (DynaBean)element; 679 } else { 680 dynaBean = new WrapDynaBean(element); 681 } 682 683 newDynaBeanType = dynaBean.getClass(); 684 685 } 686 687 // Re-calculate the element type 688 newElementType = dynaBean.getClass(); 689 if (WrapDynaBean.class.isAssignableFrom(newDynaBeanType)) { 690 newElementType = ((WrapDynaBean)dynaBean).getInstance().getClass(); 691 } else if (LazyDynaMap.class.isAssignableFrom(newDynaBeanType)) { 692 newElementType = ((LazyDynaMap)dynaBean).getMap().getClass(); 693 } 694 695 // Check the new element type, matches all the 696 // other elements in the List 697 if (elementType != null && !newElementType.equals(elementType)) { 698 throw new IllegalArgumentException("Element Type " + newElementType 699 + " doesn't match other elements " + elementType); 700 } 701 702 return dynaBean; 703 704 } 705}