CollectionsZ.java

package net.zer0bandwidth.android.lib.util;

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

/**
 * Provides additional utility methods for working with collections, beyond the
 * stock {@link Arrays} and {@link java.util.Collections} classes.
 *
 * The class is meant to look like the {@link java.util.Collections} class in
 * use. For methods that can be written statically, the class may be used as
 * such. However, certain operations that need tighter links to particular class
 * specifications require explicit type definition in the method prototypes.
 * To support this, the class provides the {@link #of} method, which creates a
 * type-bound instance of the utility class, whose instance methods may then
 * operate under this more controlled context. {@link #arrayConcat} is an
 * illustrative example of a method that needs this context:
 *
 * <pre>
 *     String[] asFoo = { "foo", "Foo", "FOO" } ;
 *     String[] asBar = { "bar", "Bar", "BAR" } ;
 *     String[] asFoobar = CollectionsZ.of( String.class )
 *             .arrayConcat( asFoo, asBar ) ;
 *     assertEquals( 6, asFoobar.length ) ;
 * </pre>
 *
 * @param <I> the class of item contained in the collection, if any
 * @since zer0bandwidth-net/android 0.1.7 (#50)
 */
public final class CollectionsZ<I>
{
	/**
	 * This method provides a static way to obtain a parameterized instance of
	 * the {@code CollectionsZ} class so that its other nifty methods may be
	 * invoked under these conditions.
	 * @param cls the class of item contained in the collection
	 * @param <T> the class of item contained in the collection
	 * @return a parameterized instance, for more finely-tuned operations
	 */
	public static <T> CollectionsZ<T> of( Class<T> cls )
	{ return new CollectionsZ<>(cls) ; }

	/**
	 * A persistent reference to the class to which the instance's generic
	 * parameter was bound at instantiation.
	 */
	private Class<I> m_cls = null ;

	/**
	 * The constructor should not be invoked directly; use {@link #of} instead.
	 * @param cls the class to which the instance's generic parameter should be
	 *            bound
	 */
	private CollectionsZ( Class<I> cls )
	{ m_cls = cls ; }

	/**
	 * Creates a new empty array of the specified size.
	 * @param nSize the initial capacity
	 * @return an array of objects of the templatized type
	 */
	@SuppressWarnings("unchecked") // guaranteed by restriction on m_cls
	protected I[] newArray( int nSize )
	{ return ((I[])( Array.newInstance( m_cls, nSize ) )) ; }

	/**
	 * Provides a sane implementation of {@code toArray()} for collections which
	 * returns an array of the object's type.
	 * @param a the collection to be converted to an array
	 * @return an array of elements in the collection, or {@code null} if the
	 *  input was also null
	 */
	public I[] toArray( Collection<I> a )
	{
		if( a == null )
			return null ;
		else if( a.size() == 0 )
			return this.newArray(0) ;
		else
			return a.toArray( this.newArray( a.size() ) ) ;
	}

	/**
	 * Concatenates an arbitrary number of arrays of the same object type
	 * @param aArrays the arrays to be concatenated
	 * @return all arrays concatenated in the order they were specified
	 */
	@SuppressWarnings("unchecked") // guaranteed logically
	public I[] arrayConcat( I[]... aArrays )
	{
		if( aArrays == null || aArrays.length == 0 ) return null ; // trivially
		if( aArrays.length == 1 ) return aArrays[0] ; // trivially

		ArrayList<I> aAll = new ArrayList<>() ;
		for( I[] aArray : aArrays )
		{
			if( aArray != null && aArray.length > 0 )
				aAll.addAll( Arrays.asList(aArray) ) ;
		}
		return aAll.toArray( this.newArray( aAll.size() ) ) ;
	}

}