All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.beanutils2.LazyDynaList Maven / Gradle / Ivy

Go to download

Apache Commons BeanUtils provides an easy-to-use but flexible wrapper around reflection and introspection.

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.beanutils2;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;

/**
 * 

Lazy DynaBean List.

* *

* There are two main purposes for this class: *

*
    *
  • To provide Lazy List behavior - automatically growing and populating the {@code List} with either * {@code DynaBean, java.util.Map} * or POJO Beans.
  • *
  • To provide a straight forward way of putting a Collection * or Array into the lazy list and a straight forward * way to get it out again at the end.
  • *
* *

All elements added to the List are stored as {@code DynaBean}'s:

*
    *
  • {@code java.util.Map elements are "wrapped" in a LazyDynaMap}.
  • *
  • POJO Bean elements are "wrapped" in a {@code WrapDynaBean}.
  • *
  • {@code DynaBean}'s are stored un-changed.
  • *
* *

{@code toArray()}

*

The {@code toArray()} method returns an array of the * elements of the appropriate type. If the {@code LazyDynaList} * is populated with {@link java.util.Map} objects a * {@code Map[]} array is returned. * If the list is populated with POJO Beans an appropriate * array of the POJO Beans is returned. Otherwise a {@code DynaBean[]} * array is returned. *

* *

{@code toDynaBeanArray()}

*

The {@code toDynaBeanArray()} method returns a * {@code DynaBean[]} array of the elements in the List. *

* *

N.B.All the elements in the List must be the * same type. If the {@code DynaClass or Class} * of the {@code LazyDynaList}'s elements is * not specified, then it will be automatically set to the type * of the first element populated. *

* *

Example 1

*

If you have an array of {@code java.util.Map[]} - you can put that into * a {@code LazyDynaList}.

* *
{@code
 *    TreeMap[] myArray = .... // your Map[]
 *    List lazyList = new LazyDynaList(myArray);
 * }
* *

New elements of the appropriate Map type are * automatically populated:

* *
{@code
 *    // get(index) automatically grows the list
 *    DynaBean newElement = (DynaBean)lazyList.get(lazyList.size());
 *    newElement.put("someProperty", "someValue");
 * }
* *

Once you've finished you can get back an Array of the * elements of the appropriate type:

* *
{@code
 *    // Retrieve the array from the list
 *    TreeMap[] myArray = (TreeMap[])lazyList.toArray());
 * }
* * *

Example 2

*

Alternatively you can create an empty List and * specify the Class for List's elements. The LazyDynaList * uses the Class to automatically populate elements:

* *
{@code
 *    // for example For Maps
 *    List lazyList = new LazyDynaList(TreeMap.class);
 *
 *    // for example For POJO Beans
 *    List lazyList = new LazyDynaList(MyPojo.class);
 *
 *    // for example For DynaBeans
 *    List lazyList = new LazyDynaList(MyDynaBean.class);
 * }
* *

Example 3

*

Alternatively you can create an empty List and specify the * DynaClass for List's elements. The LazyDynaList uses * the DynaClass to automatically populate elements:

* *
{@code
 *    // for example For Maps
 *    DynaClass dynaClass = new LazyDynaMap(new HashMap());
 *    List lazyList = new LazyDynaList(dynaClass);
 *
 *    // for example For POJO Beans
 *    DynaClass dynaClass = (new WrapDynaBean(myPojo)).getDynaClass();
 *    List lazyList = new LazyDynaList(dynaClass);
 *
 *    // for example For DynaBeans
 *    DynaClass dynaClass = new BasicDynaClass(properties);
 *    List lazyList = new LazyDynaList(dynaClass);
 * }
* *

N.B. You may wonder why control the type * using a {@code DynaClass rather than the Class} as in the previous example - the reason is that some {@code DynaBean} implementations don't * have a default empty constructor and therefore need to be instantiated using the {@code DynaClass.newInstance()} method. *

* *

Example 4

*

* A slight variation - set the element type using either the {@code setElementType(Class)} method or the {@code setElementDynaClass(DynaClass)} method - then * populate with the normal {@link java.util.List} methods (i.e. {@code add()}, {@code addAll()} or {@code set()}). *

* *
{@code
 * // Create a new LazyDynaList (100 element capacity)
 * LazyDynaList lazyList = new LazyDynaList(100);
 *
 * // Either Set the element type...
 * lazyList.setElementType(TreeMap.class);
 *
 * // ...or the element DynaClass...
 * lazyList.setElementDynaClass(new MyCustomDynaClass());
 *
 * // Populate from a collection
 * lazyList.addAll(myCollection);
 *
 * }
* * @since 1.8.0 */ public class LazyDynaList extends ArrayList { private static final long serialVersionUID = 1L; /** * The DynaClass of the List's elements. */ private DynaClass elementDynaClass; /** * The WrapDynaClass if the List's contains POJO Bean elements. * * WrapDynaClass isn't serializable, which is why its stored separately in a transient instance variable. */ private transient WrapDynaClass wrapDynaClass; /** * The type of the List's elements. */ private Class elementType; /** * The DynaBean type of the List's elements. */ private Class elementDynaBeanType; /** * Constructs a new instance. */ public LazyDynaList() { } /** * Constructs a LazyDynaList with a specified type for its elements. * * @param elementType The Type of the List's elements. */ public LazyDynaList(final Class elementType) { setElementType(elementType); } /** * Constructs a LazyDynaList populated with the elements of a Collection. * * @param collection The Collection to populate the List from. */ public LazyDynaList(final Collection collection) { super(collection.size()); addAll(collection); } /** * Constructs a LazyDynaList with a specified DynaClass for its elements. * * @param elementDynaClass The DynaClass of the List's elements. */ public LazyDynaList(final DynaClass elementDynaClass) { setElementDynaClass(elementDynaClass); } /** * Constructs a LazyDynaList with the specified capacity. * * @param capacity The initial capacity of the list. */ public LazyDynaList(final int capacity) { super(capacity); } /** * Constructs a LazyDynaList populated with the elements of an Array. * * @param array The Array to populate the List from. */ public LazyDynaList(final Object[] array) { super(array.length); this.addAll(Arrays.asList(array)); } /** *

* Insert an element at the specified index position. *

* *

* If the index position is greater than the current size of the List, then the List is automatically grown to the appropriate size. *

* * @param index The index position to insert the new element. * @param element The new element to add. */ @Override public void add(final int index, final Object element) { final DynaBean dynaBean = transform(element); growList(index); super.add(index, dynaBean); } /** *

* Add an element to the List. *

* * @param element The new element to add. * @return true. */ @Override public boolean add(final Object element) { final DynaBean dynaBean = transform(element); return super.add(dynaBean); } /** *

* Add all the elements from a Collection to the list. * * @param collection The Collection of new elements. * @return true if elements were added. */ @Override public boolean addAll(final Collection collection) { if (collection == null || collection.isEmpty()) { return false; } ensureCapacity(size() + collection.size()); collection.forEach(this::add); return true; } /** *

* Insert all the elements from a Collection into the list at a specified position. * *

* If the index position is greater than the current size of the List, then the List is automatically grown to the appropriate size. *

* * @param collection The Collection of new elements. * @param index The index position to insert the new elements at. * @return true if elements were added. */ @Override public boolean addAll(final int index, final Collection collection) { if (collection == null || collection.isEmpty()) { return false; } ensureCapacity(Math.max(index, size()) + collection.size()); // Call "transform" with first element, before // List is "grown" to ensure the correct DynaClass // is set. if (isEmpty()) { transform(collection.iterator().next()); } growList(index); int currentIndex = index; for (final Object e : collection) { add(currentIndex++, e); } return true; } /** * Creates a new {@code LazyDynaMap} object for the given property value. * * @param value the property value * @return the newly created {@code LazyDynaMap} */ private LazyDynaMap createDynaBeanForMapProperty(final Object value) { @SuppressWarnings("unchecked") final // map properties are always stored as Map Map valueMap = (Map) value; return new LazyDynaMap(valueMap); } /** *

* Return the element at the specified position. *

* *

* If the position requested is greater than the current size of the List, then the List is automatically grown (and populated) to the appropriate * size. *

* * @param index The index position to insert the new elements at. * @return The element at the specified position. */ @Override public Object get(final int index) { growList(index + 1); return super.get(index); } /** * Gets the DynaClass. */ private DynaClass getDynaClass() { return elementDynaClass == null ? wrapDynaClass : elementDynaClass; } /** *

* Automatically grown the List to the appropriate size, populating with DynaBeans. *

* * @param requiredSize the required size of the List. */ private void growList(final int requiredSize) { if (requiredSize < size()) { return; } ensureCapacity(requiredSize + 1); for (int i = size(); i < requiredSize; i++) { final DynaBean dynaBean = transform(null); super.add(dynaBean); } } /** *

* Set the element at the specified position. *

* *

* If the position requested is greater than the current size of the List, then the List is automatically grown (and populated) to the appropriate * size. *

* * @param index The index position to insert the new element at. * @param element The new element. * @return The new element. */ @Override public Object set(final int index, final Object element) { final DynaBean dynaBean = transform(element); growList(index + 1); return super.set(index, dynaBean); } /** *

* Set the element Type and DynaClass. *

* * @param elementDynaClass The DynaClass of the elements. * @throws IllegalArgumentException if the List already contains elements or the DynaClass is null. */ public void setElementDynaClass(final DynaClass elementDynaClass) { Objects.requireNonNull(elementDynaClass, "elementDynaClass"); if (!isEmpty()) { throw new IllegalStateException("Element DynaClass cannot be reset"); } // Try to create a new instance of the DynaBean try { final DynaBean dynaBean = elementDynaClass.newInstance(); this.elementDynaBeanType = dynaBean.getClass(); if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType)) { this.elementType = ((WrapDynaBean) dynaBean).getInstance().getClass(); this.wrapDynaClass = (WrapDynaClass) elementDynaClass; } else { if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType)) { this.elementType = ((LazyDynaMap) dynaBean).getMap().getClass(); } else { this.elementType = dynaBean.getClass(); } this.elementDynaClass = elementDynaClass; } } catch (final Exception e) { throw new IllegalArgumentException("Error creating DynaBean from " + elementDynaClass.getClass().getName() + " - " + e); } } /** *

* Set the element Type and DynaClass. *

* * @param elementType The type of the elements. * @throws IllegalArgumentException if the List already contains elements or the DynaClass is null. */ public void setElementType(final Class elementType) { Objects.requireNonNull(elementType, "elementType"); final boolean changeType = this.elementType != null && !this.elementType.equals(elementType); if (changeType && !isEmpty()) { throw new IllegalStateException("Element Type cannot be reset"); } this.elementType = elementType; // Create a new object of the specified type Object object = null; try { object = elementType.newInstance(); } catch (final Exception e) { throw new IllegalArgumentException("Error creating type: " + elementType.getName() + " - " + e); } // Create a DynaBean DynaBean dynaBean = null; if (Map.class.isAssignableFrom(elementType)) { dynaBean = createDynaBeanForMapProperty(object); this.elementDynaClass = dynaBean.getDynaClass(); } else if (DynaBean.class.isAssignableFrom(elementType)) { dynaBean = (DynaBean) object; this.elementDynaClass = dynaBean.getDynaClass(); } else { dynaBean = new WrapDynaBean(object); this.wrapDynaClass = (WrapDynaClass) dynaBean.getDynaClass(); } this.elementDynaBeanType = dynaBean.getClass(); // Re-calculate the type if (WrapDynaBean.class.isAssignableFrom(elementDynaBeanType)) { this.elementType = ((WrapDynaBean) dynaBean).getInstance().getClass(); } else if (LazyDynaMap.class.isAssignableFrom(elementDynaBeanType)) { this.elementType = ((LazyDynaMap) dynaBean).getMap().getClass(); } } /** *

* Converts the List to an Array. *

* *

* The type of Array created depends on the contents of the List: *

*
    *
  • If the List contains only LazyDynaMap type elements then a java.util.Map[] array will be created.
  • *
  • If the List contains only elements which are "wrapped" DynaBeans then an Object[] of the most suitable type will be created.
  • *
  • ...otherwise a DynaBean[] will be created.
  • *
* * @return An Array of the elements in this List. */ @Override public Object[] toArray() { if (isEmpty() && elementType == null) { return LazyDynaBean.EMPTY_ARRAY; } final Object[] array = (Object[]) Array.newInstance(elementType, size()); for (int i = 0; i < size(); i++) { if (Map.class.isAssignableFrom(elementType)) { array[i] = ((LazyDynaMap) get(i)).getMap(); } else if (DynaBean.class.isAssignableFrom(elementType)) { array[i] = get(i); } else { array[i] = ((WrapDynaBean) get(i)).getInstance(); } } return array; } /** *

* Converts the List to an Array of the specified type. *

* * @param The type of the array elements * @param model The model for the type of array to return * @return An Array of the elements in this List. */ @Override public T[] toArray(final T[] model) { final Class arrayType = model.getClass().getComponentType(); if (DynaBean.class.isAssignableFrom(arrayType) || isEmpty() && elementType == null) { return super.toArray(model); } if (arrayType.isAssignableFrom(elementType)) { T[] array; if (model.length >= size()) { array = model; } else { @SuppressWarnings("unchecked") final // This is safe because we know the element type T[] tempArray = (T[]) Array.newInstance(arrayType, size()); array = tempArray; } for (int i = 0; i < size(); i++) { Object elem; if (Map.class.isAssignableFrom(elementType)) { elem = ((LazyDynaMap) get(i)).getMap(); } else if (DynaBean.class.isAssignableFrom(elementType)) { elem = get(i); } else { elem = ((WrapDynaBean) get(i)).getInstance(); } Array.set(array, i, elem); } return array; } throw new IllegalArgumentException("Invalid array type: " + arrayType.getName() + " - not compatible with '" + elementType.getName()); } /** *

* Converts the List to an DynaBean Array. *

* * @return A DynaBean[] of the elements in this List. */ public DynaBean[] toDynaBeanArray() { if (isEmpty() && elementDynaBeanType == null) { return LazyDynaBean.EMPTY_ARRAY; } final DynaBean[] array = (DynaBean[]) Array.newInstance(elementDynaBeanType, size()); for (int i = 0; i < size(); i++) { array[i] = (DynaBean) get(i); } return array; } /** *

* Transform the element into a DynaBean: *

* *
    *
  • Map elements are turned into LazyDynaMap's.
  • *
  • POJO Beans are "wrapped" in a WrapDynaBean.
  • *
  • DynaBeans are unchanged.
  • * * @param element The element to transformed. * @return The DynaBean to store in the List. */ private DynaBean transform(final Object element) { DynaBean dynaBean = null; Class newDynaBeanType = null; Class newElementType; // Create a new element if (element == null) { // Default Types to LazyDynaBean // if not specified if (elementType == null) { setElementDynaClass(new LazyDynaClass()); } // Get DynaClass (restore WrapDynaClass lost in serialization) if (getDynaClass() == null) { setElementType(elementType); } // Create a new DynaBean try { dynaBean = getDynaClass().newInstance(); newDynaBeanType = dynaBean.getClass(); } catch (final Exception e) { throw new IllegalArgumentException("Error creating DynaBean: " + getDynaClass().getClass().getName() + " - " + e); } } else { // Transform Object to a DynaBean newElementType = element.getClass(); if (Map.class.isAssignableFrom(element.getClass())) { dynaBean = createDynaBeanForMapProperty(element); } else if (DynaBean.class.isAssignableFrom(element.getClass())) { dynaBean = (DynaBean) element; } else { dynaBean = new WrapDynaBean(element); } newDynaBeanType = dynaBean.getClass(); } // Re-calculate the element type newElementType = dynaBean.getClass(); if (WrapDynaBean.class.isAssignableFrom(newDynaBeanType)) { newElementType = ((WrapDynaBean) dynaBean).getInstance().getClass(); } else if (LazyDynaMap.class.isAssignableFrom(newDynaBeanType)) { newElementType = ((LazyDynaMap) dynaBean).getMap().getClass(); } // Check the new element type, matches all the // other elements in the List if (elementType != null && !newElementType.equals(elementType)) { throw new IllegalArgumentException("Element Type " + newElementType + " doesn't match other elements " + elementType); } return dynaBean; } }